Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3fcd372
initial prototype
JennyPng Oct 13, 2025
979bd7e
many fixes
JennyPng Oct 13, 2025
fa499ca
Merge branch 'main' into verifysetup
JennyPng Oct 13, 2025
2199200
more fix json deserialization
JennyPng Oct 13, 2025
ecdb821
command type fix
JennyPng Oct 13, 2025
d838aa7
basic functionality working
JennyPng Oct 13, 2025
cc460e3
cleanup and input validation
JennyPng Oct 13, 2025
46a5283
minor fix
JennyPng Oct 13, 2025
26eb36d
start integrating languageresolver
JennyPng Oct 14, 2025
c463454
interface fix
JennyPng Oct 14, 2025
e8bfdd7
add per language requirements getters for everything
JennyPng Oct 14, 2025
28eb265
add packagePath arg so language detection actually works
JennyPng Oct 15, 2025
aa0f733
very scrappy but working pythonprocesshelper
JennyPng Oct 15, 2025
959db7e
minor clean
JennyPng Oct 15, 2025
9dddc06
java reqs
JennyPng Oct 16, 2025
8ff91b5
add command output to tool response
JennyPng Oct 16, 2025
b8af98e
read core reqs
JennyPng Oct 16, 2025
f071387
remaining reqs
JennyPng Oct 16, 2025
5248427
refactor per-language req getting
JennyPng Oct 16, 2025
a506799
go reqs
JennyPng Oct 16, 2025
baa135b
add tool instructions prompt
JennyPng Oct 17, 2025
0fe6578
minor
JennyPng Oct 17, 2025
791b4f0
simplify python venv logic
JennyPng Oct 17, 2025
243d3ad
minor rename
JennyPng Oct 17, 2025
badc0bd
add version comparison
JennyPng Oct 17, 2025
865dbf9
clean
JennyPng Oct 17, 2025
4891e85
Merge branch 'main' into verifysetup
JennyPng Oct 17, 2025
157d7b8
updates after merging main
JennyPng Oct 17, 2025
2308cb2
prompt revision
JennyPng Oct 17, 2025
8d3bd6c
prompt revision and fallback to core requirements
JennyPng Oct 17, 2025
8b720fc
update reqs
JennyPng Oct 20, 2025
3f69c85
unit tests
JennyPng Oct 20, 2025
3ebf237
Merge branch 'main' into verifysetup
JennyPng Oct 20, 2025
93fb434
fixes after merging
JennyPng Oct 20, 2025
ed01376
fix
JennyPng Oct 20, 2025
63d69a3
rename langs to languages + revise prompt
JennyPng Oct 20, 2025
3ea358c
edit prompt and make checks concurrent
JennyPng Oct 21, 2025
25e7b38
update python reqs
JennyPng Oct 21, 2025
3c0a1bc
fix unit tests
JennyPng Oct 21, 2025
46dd780
Merge branch 'main' into verifysetup
JennyPng Oct 21, 2025
bbe0de2
update python reqs
JennyPng Oct 21, 2025
06c8bc9
refactor to accept SdkLanguage instead of string input
JennyPng Oct 21, 2025
db2d67d
read reqs from assembly
JennyPng Oct 22, 2025
ec41f62
log and exception catching fixes + req update
JennyPng Oct 22, 2025
23534bf
add responseerror
JennyPng Oct 22, 2025
04594de
fix prompt
JennyPng Oct 22, 2025
76bff57
remove redundant error output
JennyPng Oct 22, 2025
c36e423
add venvPath argument
JennyPng Oct 23, 2025
dda9f51
Merge branch 'main' into verifysetup
JennyPng Oct 23, 2025
6886a33
use hashset, add python to core req, other minor fixes
JennyPng Oct 23, 2025
8464b01
use system.version parsing
JennyPng Oct 23, 2025
a99e6b9
update dotnet version
JennyPng Oct 23, 2025
e674752
refactor to deserialize json once and use as param
JennyPng Oct 23, 2025
ef1ab80
minor prompt and log edit
JennyPng Oct 24, 2025
8893e79
Merge branch 'main' into verifysetup
JennyPng Oct 27, 2025
c9c13b4
remove auto venv creation
JennyPng Oct 27, 2025
171ffc6
Merge branch 'main' into verifysetup
JennyPng Oct 27, 2025
4a3fa9d
refactor overload resolve function
JennyPng Oct 28, 2025
86afeb5
minor
JennyPng Oct 28, 2025
7687a7e
Merge branch 'main' into verifysetup
JennyPng Oct 31, 2025
a358c7d
merge fix
JennyPng Oct 31, 2025
b799184
remove unneeded response field
JennyPng Oct 31, 2025
e5644a3
Merge branch 'main' into verifysetup
JennyPng Nov 3, 2025
f5c5e06
update docs and use consts for regex
JennyPng Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
description: 'Verify Setup'
---

## Goal
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 of the repo.

## Examples
- in `azure-sdk-for-js`, run `azsdk_verify_setup` with `(langs=javascript, packagePath=<path>/azure-sdk-for-js)`.
- in `azure-sdk-for-python`, run `azsdk_verify_setup` with `(langs=python, packagePath=<path>/azure-sdk-for-python, venvPath=<path-to-venv>)`.

## Parameter Requirements
WHENEVER Python is included in `langs`, BEFORE RUNNING `azsdk_verify_setup`, you MUST ASK THE USER TO SPECIFY WHICH virtual environment they want to check. DO NOT ASSUME THE VENV WITHOUT ASKING THE USER. After obtaining the `venvPath`, you can run the tool.

The user can specify multiple languages to check. If the user wants to check all languages, pass in ALL supported languages and STILL ASK for a `venvPath`. Passing in no languages will only check the core requirements.

## Output
Display results in a user-friendly and concise format, highlighting any missing dependencies that need to be addressed.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private class SingleResolver : ILanguageSpecificResolver<IClientUpdateLanguageSe
private readonly IClientUpdateLanguageService _svc;
public SingleResolver(IClientUpdateLanguageService svc) { _svc = svc; }
public Task<IClientUpdateLanguageService?> Resolve(string packagePath, CancellationToken ct = default) => Task.FromResult(_svc);
public Task<IClientUpdateLanguageService?> Resolve(SdkLanguage language, CancellationToken ct = default) => Task.FromResult(_svc);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// 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.Contains(command) || opt.Args.Contains(command)), // Handle cases where command might be wrapped
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.Contains(command) || opt.Args.Contains(command)),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new ProcessResult
{
ExitCode = exitCode,
OutputDetails = new List<(StdioLevel, string)> { (StdioLevel.StandardError, errorOutput) }
});
}

private void SetupLanguageRequirementsMocks(Dictionary<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)> languageSpecs)
{
mockEnvRequirementsCheck
.Setup(x => x.Resolve(It.IsAny<SdkLanguage>(), It.IsAny<CancellationToken>()))
.Returns((SdkLanguage lang, CancellationToken _) =>
{
if (languageSpecs.ContainsKey(lang))
{
var mockChecker = new Mock<IEnvRequirementsCheck>();
var spec = languageSpecs[lang];
mockChecker
.Setup(x => x.GetRequirements(It.IsAny<string>(), It.IsAny<Dictionary<string, List<SetupRequirements.Requirement>>>(), It.IsAny<CancellationToken>()))
.Returns(new List<SetupRequirements.Requirement>
{
new SetupRequirements.Requirement
{
requirement = spec.requirement,
check = spec.checkCommand,
instructions = spec.instructions
}
});
return Task.FromResult<IEnvRequirementsCheck?>(mockChecker.Object);
}
else
{
return Task.FromResult<IEnvRequirementsCheck?>(null); // Language not supported
}
});
}

[Test]
public async Task VerifySetup_Succeeds_WhenAllRequirementsMet()
{
// Arrange
var languageSpecs = new Dictionary<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)>
{
{ SdkLanguage.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 HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path");

// Assert
Assert.That(result.Results, Is.Empty);
Assert.That(result.ResponseError, Is.Null);
}

[Test]
public async Task VerifySetup_Fails_WhenSomeRequirementsNotMet()
{
var languageSpecs = new Dictionary<SdkLanguage, (string, string[], List<string>)>
{
{ SdkLanguage.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 HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path");

// Assert
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<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)>
{
{ SdkLanguage.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 HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path");

// Assert
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<SdkLanguage, (string, string[], List<string>)>
{
{ SdkLanguage.Python, ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8" }) },
{ SdkLanguage.Java, ("Java >= 17", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) },
{ SdkLanguage.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 HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path");

// Assert
Assert.That(result.ResponseError, Is.Null);

// Verify that only Python language resolver was called, not Java or .NET
mockEnvRequirementsCheck.Verify(
x => x.Resolve(SdkLanguage.Python, It.IsAny<CancellationToken>()),
Times.Once);

mockEnvRequirementsCheck.Verify(
x => x.Resolve(SdkLanguage.Java, It.IsAny<CancellationToken>()),
Times.Never);

mockEnvRequirementsCheck.Verify(
x => x.Resolve(SdkLanguage.DotNet, It.IsAny<CancellationToken>()),
Times.Never);
}

[Test]
public async Task VerifySetup_ChecksMultipleSpecifiedLanguages()
{
// Arrange
var languageSpecs = new Dictionary<SdkLanguage, (string, string[], List<string>)>
{
{ SdkLanguage.Python, ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8" }) },
{ SdkLanguage.Java, ("Java >= 17.0", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) }
};

SetupLanguageRequirementsMocks(languageSpecs);

// Act - Request both Python and Java
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python, SdkLanguage.Java }, "/test/path");

// Assert
Assert.That(result.ResponseError, Is.Null);

// Verify that resolver was called for both languages
mockEnvRequirementsCheck.Verify(
x => x.Resolve(SdkLanguage.Python, It.IsAny<CancellationToken>()),
Times.Once);

mockEnvRequirementsCheck.Verify(
x => x.Resolve(SdkLanguage.Java, It.IsAny<CancellationToken>()),
Times.Once);
}

[Test]
public async Task VerifySetup_HandlesInvalidLanguageInput()
{
// Mock the resolver to return null for invalid languages
mockEnvRequirementsCheck
.Setup(x => x.Resolve(It.IsAny<SdkLanguage>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((IEnvRequirementsCheck?)null);

// Act - Pass invalid language
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { (SdkLanguage)(-1) }, "/test/path");

// Assert - Should succeed with just core requirements
Assert.That(result.ResponseError, Is.Null);
Assert.That(result.Results, Is.Empty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
<EmbeddedResource Include="Images/quokka.ansi" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Configuration/RequirementsV1.json" />
</ItemGroup>

<ItemGroup>
<!-- Include changelog and documentation files in the package -->
<None Include="CHANGELOG.md" Pack="true" PackagePath="CHANGELOG.md" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public static class SharedCommandGroups
Options: []
);

public static readonly CommandGroup Verify = new(
Verb: "verify",
Description: "Tools for verifying project environments.",
Options: []
);

#if DEBUG
public static readonly CommandGroup Example = new(
Verb: "example",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Azure.Sdk.Tools.Cli.Tools.ReleasePlan;
using Azure.Sdk.Tools.Cli.Tools.Example;
using Azure.Sdk.Tools.Cli.Tools.TypeSpec;
using Azure.Sdk.Tools.Cli.Tools.Verify;
using Azure.Sdk.Tools.Cli.Tools.Samples;

namespace Azure.Sdk.Tools.Cli.Commands
Expand Down Expand Up @@ -39,6 +40,7 @@ public static class SharedOptions
typeof(TypeSpecInitTool),
typeof(TspClientUpdateTool),
typeof(TypeSpecPublicRepoValidationTool),
typeof(VerifySetupTool),
typeof(TestTool),
#if DEBUG
// only add these tools in debug mode
Expand Down
Loading