-
Notifications
You must be signed in to change notification settings - Fork 256
Add Verify Setup Tool #12476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add Verify Setup Tool #12476
Changes from 37 commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
3fcd372
initial prototype
JennyPng 979bd7e
many fixes
JennyPng fa499ca
Merge branch 'main' into verifysetup
JennyPng 2199200
more fix json deserialization
JennyPng ecdb821
command type fix
JennyPng d838aa7
basic functionality working
JennyPng cc460e3
cleanup and input validation
JennyPng 46a5283
minor fix
JennyPng 26eb36d
start integrating languageresolver
JennyPng c463454
interface fix
JennyPng e8bfdd7
add per language requirements getters for everything
JennyPng 28eb265
add packagePath arg so language detection actually works
JennyPng aa0f733
very scrappy but working pythonprocesshelper
JennyPng 959db7e
minor clean
JennyPng 9dddc06
java reqs
JennyPng 8ff91b5
add command output to tool response
JennyPng b8af98e
read core reqs
JennyPng f071387
remaining reqs
JennyPng 5248427
refactor per-language req getting
JennyPng a506799
go reqs
JennyPng baa135b
add tool instructions prompt
JennyPng 0fe6578
minor
JennyPng 791b4f0
simplify python venv logic
JennyPng 243d3ad
minor rename
JennyPng badc0bd
add version comparison
JennyPng 865dbf9
clean
JennyPng 4891e85
Merge branch 'main' into verifysetup
JennyPng 157d7b8
updates after merging main
JennyPng 2308cb2
prompt revision
JennyPng 8d3bd6c
prompt revision and fallback to core requirements
JennyPng 8b720fc
update reqs
JennyPng 3f69c85
unit tests
JennyPng 3ebf237
Merge branch 'main' into verifysetup
JennyPng 93fb434
fixes after merging
JennyPng ed01376
fix
JennyPng 63d69a3
rename langs to languages + revise prompt
JennyPng 3ea358c
edit prompt and make checks concurrent
JennyPng 25e7b38
update python reqs
JennyPng 3c0a1bc
fix unit tests
JennyPng 46dd780
Merge branch 'main' into verifysetup
JennyPng bbe0de2
update python reqs
JennyPng 06c8bc9
refactor to accept SdkLanguage instead of string input
JennyPng db2d67d
read reqs from assembly
JennyPng ec41f62
log and exception catching fixes + req update
JennyPng 23534bf
add responseerror
JennyPng 04594de
fix prompt
JennyPng 76bff57
remove redundant error output
JennyPng c36e423
add venvPath argument
JennyPng dda9f51
Merge branch 'main' into verifysetup
JennyPng 6886a33
use hashset, add python to core req, other minor fixes
JennyPng 8464b01
use system.version parsing
JennyPng a99e6b9
update dotnet version
JennyPng e674752
refactor to deserialize json once and use as param
JennyPng ef1ab80
minor prompt and log edit
JennyPng 8893e79
Merge branch 'main' into verifysetup
JennyPng c9c13b4
remove auto venv creation
JennyPng 171ffc6
Merge branch 'main' into verifysetup
JennyPng 4a3fa9d
refactor overload resolve function
JennyPng 86afeb5
minor
JennyPng 7687a7e
Merge branch 'main' into verifysetup
JennyPng a358c7d
merge fix
JennyPng b799184
remove unneeded response field
JennyPng e5644a3
Merge branch 'main' into verifysetup
JennyPng f5c5e06
update docs and use consts for regex
JennyPng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
eng/common/instructions/azsdk-tools/verify-setup.instructions.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| description: 'Verify Setup' | ||
| --- | ||
| This tool verifies the developer's environment for SDK development and release tasks. It returns what requirements are missing for the specified languages and repo. | ||
|
|
||
| Your goal is to identify the project repo root, and pass in the packagePath to the Verify Setup tool. For a language repo, pass in the language. For a non-language repo, do not pass in any languages to just check the core requirements. Summarize the output of the tool. | ||
|
|
||
| For example, in `azure-sdk-for-js`, run `VerifySetup` with `(langs=javascript, packagePath=<path>/azure-sdk-for-js)`. | ||
|
|
||
| Display results in a user-friendly and concise format, highlighting any missing dependencies or configuration issues that need to be addressed. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
248 changes: 248 additions & 0 deletions
248
tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Tools/Verify/VerifySetupToolTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
| using System.CommandLine; | ||
| using Moq; | ||
| using Azure.Sdk.Tools.Cli.Helpers; | ||
| using Azure.Sdk.Tools.Cli.Models; | ||
| using Azure.Sdk.Tools.Cli.Services; | ||
| using Azure.Sdk.Tools.Cli.Services.VerifySetup; | ||
| using Azure.Sdk.Tools.Cli.Tests.TestHelpers; | ||
| using Azure.Sdk.Tools.Cli.Tools.Verify; | ||
|
|
||
| namespace Azure.Sdk.Tools.Cli.Tests.Tools.Verify; | ||
|
|
||
| internal class VerifySetupToolTests | ||
| { | ||
| private VerifySetupTool tool; | ||
| private Mock<IProcessHelper> mockProcessHelper; | ||
| private Mock<ILanguageSpecificResolver<IEnvRequirementsCheck>> mockEnvRequirementsCheck; | ||
| private TestLogger<VerifySetupTool> logger; | ||
|
|
||
| [SetUp] | ||
| public void Setup() | ||
| { | ||
| mockProcessHelper = new Mock<IProcessHelper>(); | ||
| mockEnvRequirementsCheck = new Mock<ILanguageSpecificResolver<IEnvRequirementsCheck>>(); | ||
| logger = new TestLogger<VerifySetupTool>(); | ||
|
|
||
| tool = new VerifySetupTool( | ||
| mockProcessHelper.Object, | ||
| logger, | ||
| mockEnvRequirementsCheck.Object | ||
| ); | ||
|
|
||
| SetupSuccessfulProcessMocks(); | ||
| } | ||
|
|
||
| private void SetupSuccessfulProcessMocks() | ||
| { | ||
| var successfulCommands = new Dictionary<string, string> | ||
| { | ||
| { "node", "v22.16.0" }, | ||
| { "npm", "10.5.0" }, | ||
| { "tsp-client", "0.24.1" }, | ||
| { "tsp", "1.0.1" }, | ||
| { "pwsh", "PowerShell 7.2.0" }, | ||
| { "gh", "gh version 2.30.0" }, | ||
| { "python", "Python 3.9.0" }, | ||
| { "java", "java 17.0.1" } | ||
| }; | ||
|
|
||
| foreach (var (command, output) in successfulCommands) | ||
| { | ||
| mockProcessHelper | ||
| .Setup(x => x.Run( | ||
| It.Is<ProcessOptions>(opt => opt.Command == command && (opt.Args.Contains("--version") || opt.Args.Contains("-version"))), | ||
| It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(new ProcessResult | ||
| { | ||
| ExitCode = 0, | ||
| OutputDetails = new List<(StdioLevel, string)> { (StdioLevel.StandardOutput, output) } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private void SetupFailedProcessMock(string command, int exitCode = 1, string errorOutput = "Command not found") | ||
| { | ||
| mockProcessHelper | ||
| .Setup(x => x.Run( | ||
| It.Is<ProcessOptions>(opt => opt.Command == command && opt.Args.Contains("--version")), | ||
| It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(new ProcessResult | ||
| { | ||
| ExitCode = exitCode, | ||
| OutputDetails = new List<(StdioLevel, string)> { (StdioLevel.StandardError, errorOutput) } | ||
| }); | ||
| } | ||
|
|
||
| private void SetupLanguageRequirementsMocks(Dictionary<string, (string requirement, string[] checkCommand, List<string> instructions)> languageSpecs) | ||
| { | ||
| mockEnvRequirementsCheck | ||
| .Setup(x => x.Resolve(It.IsAny<List<string>>(), It.IsAny<CancellationToken>())) | ||
| .Returns((List<string> langs, CancellationToken _) => | ||
| { | ||
| var checkers = new List<IEnvRequirementsCheck?>(); | ||
| foreach (var lang in langs) | ||
| { | ||
| if (languageSpecs.ContainsKey(lang)) | ||
| { | ||
| var mockChecker = new Mock<IEnvRequirementsCheck>(); | ||
| var spec = languageSpecs[lang]; | ||
| mockChecker | ||
| .Setup(x => x.GetRequirements(It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(new List<SetupRequirements.Requirement> | ||
| { | ||
| new SetupRequirements.Requirement | ||
| { | ||
| requirement = spec.requirement, | ||
| check = spec.checkCommand, | ||
| instructions = spec.instructions | ||
| } | ||
| }); | ||
| checkers.Add(mockChecker.Object); | ||
| } | ||
| else | ||
| { | ||
| checkers.Add(null); // Language not supported | ||
| } | ||
| } | ||
| return checkers; | ||
| }); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_Succeeds_WhenAllRequirementsMet() | ||
| { | ||
| // Arrange | ||
| var languageSpecs = new Dictionary<string, (string, string[], List<string>)> | ||
| { | ||
| { "python", ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8 or higher" }) } | ||
| }; | ||
| SetupLanguageRequirementsMocks(languageSpecs); | ||
|
|
||
| // Act | ||
| var result = await tool.VerifySetup(new List<string> { "python" }, "/test/path"); | ||
|
|
||
| // Assert | ||
| Assert.That(result.AllRequirementsSatisfied, Is.True); | ||
| Assert.That(result.Results, Is.Empty); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_Fails_WhenSomeRequirementsNotMet() | ||
| { | ||
| var languageSpecs = new Dictionary<string, (string, string[], List<string>)> | ||
| { | ||
| { "python", ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8 or higher" }) } | ||
| }; | ||
| SetupLanguageRequirementsMocks(languageSpecs); | ||
|
|
||
| SetupFailedProcessMock("node", 1, "node: command not found"); | ||
|
|
||
| // Act | ||
| var result = await tool.VerifySetup(new List<string> { "python" }, "/test/path"); | ||
|
|
||
| // Assert | ||
| Assert.That(result.AllRequirementsSatisfied, Is.False); | ||
| Assert.That(result.Results, Is.Not.Empty); | ||
| Assert.That(result.Results.Any(r => r.Requirement.Contains("Node.js")), Is.True); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_Fails_WhenSomeRequirementsVersionNotMet() | ||
| { | ||
| var languageSpecs = new Dictionary<string, (string, string[], List<string>)> | ||
| { | ||
| { "python", ("Python >= 3.14", new[] { "python", "--version" }, new List<string> { "Install Python 3.14 or higher" }) } | ||
| }; | ||
| SetupLanguageRequirementsMocks(languageSpecs); | ||
|
|
||
| // Act | ||
| var result = await tool.VerifySetup(new List<string> { "python" }, "/test/path"); | ||
|
|
||
| // Assert | ||
| Assert.That(result.AllRequirementsSatisfied, Is.False); | ||
| Assert.That(result.Results, Is.Not.Empty); | ||
| Assert.That(result.Results.Any(r => r.Requirement.Contains("Python")), Is.True); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_OnlyChecksSpecifiedLanguages() | ||
| { | ||
| // Arrange - Set up multiple language specs, but only request python | ||
| var languageSpecs = new Dictionary<string, (string, string[], List<string>)> | ||
| { | ||
| { "python", ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8" }) }, | ||
| { "java", ("Java >= 17", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) }, | ||
| { "dotnet", (".NET >= 8.0", new[] { "dotnet", "--version" }, new List<string> { "Install .NET 8.0" }) } | ||
| }; | ||
|
|
||
| SetupLanguageRequirementsMocks(languageSpecs); | ||
| SetupFailedProcessMock("java", 1, "java: command not found"); | ||
|
|
||
| // Act | ||
| var result = await tool.VerifySetup(new List<string> { "python" }, "/test/path"); | ||
|
|
||
| // Assert | ||
| Assert.That(result.AllRequirementsSatisfied, Is.True); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
|
|
||
| // Verify that only Python language resolver was called, not Java or .NET | ||
| mockEnvRequirementsCheck.Verify( | ||
| x => x.Resolve(It.Is<List<string>>(langs => langs.Contains("python") && langs.Count == 1), It.IsAny<CancellationToken>()), | ||
| Times.Once); | ||
|
|
||
| mockEnvRequirementsCheck.Verify( | ||
| x => x.Resolve(It.Is<List<string>>(langs => langs.Contains("java")), It.IsAny<CancellationToken>()), | ||
| Times.Never); | ||
|
|
||
| mockEnvRequirementsCheck.Verify( | ||
| x => x.Resolve(It.Is<List<string>>(langs => langs.Contains("dotnet")), It.IsAny<CancellationToken>()), | ||
| Times.Never); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_ChecksMultipleSpecifiedLanguages() | ||
| { | ||
| // Arrange | ||
| var languageSpecs = new Dictionary<string, (string, string[], List<string>)> | ||
| { | ||
| { "python", ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8" }) }, | ||
| { "java", ("Java >= 17", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) } | ||
| }; | ||
|
|
||
| SetupLanguageRequirementsMocks(languageSpecs); | ||
|
|
||
| // Act - Request both Python and Java | ||
| var result = await tool.VerifySetup(new List<string> { "python", "java" }, "/test/path"); | ||
|
|
||
| // Assert | ||
| Assert.That(result.AllRequirementsSatisfied, Is.True); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
|
|
||
| // Verify that resolver was called with both languages | ||
| mockEnvRequirementsCheck.Verify( | ||
| x => x.Resolve(It.Is<List<string>>(langs => langs.Contains("python") && langs.Contains("java") && langs.Count == 2), It.IsAny<CancellationToken>()), | ||
| Times.Once); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task VerifySetup_HandlesInvalidLanguageInput() | ||
| { | ||
| // Mock the resolver to return empty list for invalid languages (simulating no valid languages found) | ||
| mockEnvRequirementsCheck | ||
| .Setup(x => x.Resolve(It.IsAny<List<string>>(), It.IsAny<CancellationToken>())) | ||
| .Returns(new List<IEnvRequirementsCheck?>()); // Return empty list, not null | ||
|
|
||
| // Act - Pass invalid language | ||
| var result = await tool.VerifySetup(new List<string> { "invalidlang" }, "/test/path"); | ||
|
|
||
| // Assert - Should succeed with just core requirements | ||
| Assert.That(result.AllRequirementsSatisfied, Is.True); | ||
| Assert.That(result.ResponseError, Is.Null); | ||
| Assert.That(result.Results, Is.Empty); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.