Skip to content

Commit 9c20107

Browse files
Refactor VerifySetup to get requirements from code (#13663)
* initial new requirement classes * python specific req handling * preemptive cleanup, need to handle multiple langs * initial verifysetup refactor, multi lang handling * clean * update tests * rest-api-spec case * cleanup * copilot comments * format * Update tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/SetupRequirements/GoRequirements.cs Co-authored-by: Richard Park <51494936+richardpark-msft@users.noreply.github.com> * move req classes * remove pwsh dependency by overriding a RunCheck instead * merge fix * address copilot comments * alternate tsp-client check for specs repo * minor * reporoot will never be null at this point * discover reponame * check for private specs repo --------- Co-authored-by: Richard Park <51494936+richardpark-msft@users.noreply.github.com>
1 parent ad5e980 commit 9c20107

21 files changed

+933
-415
lines changed

tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Tools/Verify/VerifySetupToolTests.cs

Lines changed: 80 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Azure.Sdk.Tools.Cli.Models;
77
using Azure.Sdk.Tools.Cli.Services;
88
using Azure.Sdk.Tools.Cli.Services.Languages;
9+
using Azure.Sdk.Tools.Cli.Services.SetupRequirements;
910
using Azure.Sdk.Tools.Cli.Tests.TestHelpers;
1011
using Azure.Sdk.Tools.Cli.Tools.Verify;
1112
using Microsoft.VisualStudio.Services.CircuitBreaker;
@@ -58,7 +59,9 @@ public void Setup()
5859
return "unknown-repo"; // default fallback
5960
});
6061

61-
// Create temp directory for tests
62+
// Mock DiscoverRepoRootAsync for PackagePathParser.ParseAsync
63+
_mockGitHelper.Setup(x => x.DiscoverRepoRootAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
64+
.ReturnsAsync((string path, CancellationToken _) => path ?? "/test/repo");
6265

6366
languageServices = [
6467
new PythonLanguageService(mockProcessHelper.Object, mockPythonHelper.Object, _mockNpxHelper.Object, _mockGitHelper.Object, _languageLogger, _commonValidationHelpers.Object, Mock.Of<IFileHelper>(), Mock.Of<ISpecGenSdkConfigHelper>()),
@@ -75,7 +78,7 @@ private void SetupSuccessfulProcessMocks()
7578
{
7679
mockProcessHelper
7780
.Setup(x => x.Run(
78-
It.IsAny<ProcessOptions>(), // Handle cases where command might be wrapped
81+
It.IsAny<ProcessOptions>(),
7982
It.IsAny<CancellationToken>()))
8083

8184
.ReturnsAsync((ProcessOptions processOptions, CancellationToken ct) =>
@@ -89,13 +92,26 @@ private void SetupSuccessfulProcessMocks()
8992
{ "pwsh", "PowerShell 7.2.0" },
9093
{ "gh", "gh version 2.30.0" },
9194
{ "python", "Python 3.9.0" },
92-
{ "java", "java 17.0.1" }
95+
{ "pip", "pip 24.0" },
96+
{ "java", "java 17.0.1" },
97+
{ "mvn", "Apache Maven 3.9.0" },
98+
{ "dotnet", "8.0.100" },
99+
{ "go", "go version go1.21.0" },
100+
{ "pnpm", "9.0.0" },
101+
{ "azpysdk", "azpysdk help" },
102+
{ "sdk_generator", "sdk_generator help" },
103+
{ "GitPython", "GitPython 3.1.0" },
104+
{ "pytest", "pytest 8.3.5" },
105+
{ "golangci-lint", "golangci-lint 1.55.0" },
106+
{ "goimports", "goimports" },
107+
{ "generator", "generator 0.4.3" }
93108
};
94109
foreach (var kvp in successfulCommands)
95110
{
96111
var command = kvp.Key;
97112
var output = kvp.Value;
98-
if (processOptions.Command.Contains(command) || processOptions.Args.Contains(command))
113+
if (processOptions.Command.Contains(command) ||
114+
processOptions.Args.Any(a => a.Contains(command)))
99115
{
100116
return new ProcessResult
101117
{
@@ -116,7 +132,7 @@ private void SetupFailedProcessMock(string command, int exitCode = 1, string err
116132
{
117133
mockProcessHelper
118134
.Setup(x => x.Run(
119-
It.Is<ProcessOptions>(opt => opt.Command.Contains(command) || opt.Args.Contains(command)),
135+
It.Is<ProcessOptions>(opt => opt.Command.Contains(command) || opt.Args.Any(a => a.Contains(command))),
120136
It.IsAny<CancellationToken>()))
121137
.ReturnsAsync(new ProcessResult
122138
{
@@ -125,174 +141,144 @@ private void SetupFailedProcessMock(string command, int exitCode = 1, string err
125141
});
126142
}
127143

128-
private List<LanguageService> SetupLanguageRequirementsMocks(Dictionary<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)> languageSpecs)
144+
private void SetupVersionMismatchMock(string command, string version)
129145
{
130-
List<LanguageService> langs = [];
131-
foreach (var kvp in languageSpecs)
132-
{
133-
var mockChecker = new Mock<LanguageService>();
134-
var spec = kvp.Value;
135-
mockChecker
136-
.Setup(x => x.GetRequirements(It.IsAny<string>(), It.IsAny<Dictionary<string, List<SetupRequirements.Requirement>>>(), It.IsAny<CancellationToken>()))
137-
.Returns(new List<SetupRequirements.Requirement>
138-
{
139-
new SetupRequirements.Requirement
140-
{
141-
requirement = spec.requirement,
142-
check = spec.checkCommand,
143-
instructions = spec.instructions
144-
}
145-
});
146-
mockChecker.Setup(x => x.Language).Returns(kvp.Key);
147-
langs.Add(mockChecker.Object);
148-
}
149-
return langs;
146+
mockProcessHelper
147+
.Setup(x => x.Run(
148+
It.Is<ProcessOptions>(opt => opt.Command.Contains(command) || opt.Args.Any(a => a.Contains(command))),
149+
It.IsAny<CancellationToken>()))
150+
.ReturnsAsync(new ProcessResult
151+
{
152+
ExitCode = 0,
153+
OutputDetails = new List<(StdioLevel, string)> { (StdioLevel.StandardOutput, version) }
154+
});
150155
}
151156

152157
[Test]
153158
public async Task VerifySetup_Succeeds_WhenAllRequirementsMet()
154159
{
155160
// Arrange
156-
var languageSpecs = new Dictionary<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)>
157-
{
158-
{ SdkLanguage.Python, ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8 or higher" }) }
159-
};
160-
var langTestServices = SetupLanguageRequirementsMocks(languageSpecs);
161-
162-
// Act
163161
var tool = new VerifySetupTool(
164162
mockProcessHelper.Object,
165163
logger,
166164
_mockGitHelper.Object,
167-
langTestServices
165+
languageServices
168166
);
167+
168+
// Act - Check Python requirements
169169
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path/python");
170170

171-
// Assert
172-
Assert.That(result.Results, Is.Empty);
171+
// Assert - Should have no failures when all mocks return success
173172
Assert.That(result.ResponseError, Is.Null);
174173
}
175174

176175
[Test]
177-
public async Task VerifySetup_Fails_WhenSomeRequirementsNotMet()
176+
public async Task VerifySetup_Fails_WhenCoreRequirementNotMet()
178177
{
179-
var languageSpecs = new Dictionary<SdkLanguage, (string, string[], List<string>)>
180-
{
181-
{ SdkLanguage.Python, ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8 or higher" }) }
182-
};
183-
var langTestServices = SetupLanguageRequirementsMocks(languageSpecs);
184-
178+
// Arrange - Node.js command fails
185179
SetupFailedProcessMock("node", 1, "node: command not found");
186180

187-
// Act
188181
var tool = new VerifySetupTool(
189182
mockProcessHelper.Object,
190183
logger,
191184
_mockGitHelper.Object,
192-
langTestServices
185+
languageServices
193186
);
187+
188+
// Act
194189
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path/python");
195190

196191
// Assert
197-
Assert.That(result.Results, Is.Not.Empty);
198-
Assert.That(result.Results.Any(r => r.Requirement.Contains("Node.js")), Is.True);
192+
Assert.That(result.Results, Is.Not.Null.And.Not.Empty);
193+
Assert.That(result.Results!.Any(r => r.Requirement.Contains("Node")), Is.True);
199194
Assert.That(result.ResponseError, Is.Null);
200195
}
201196

202197
[Test]
203-
public async Task VerifySetup_Fails_WhenSomeRequirementsVersionNotMet()
198+
public async Task VerifySetup_Fails_WhenVersionRequirementNotMet()
204199
{
205-
var languageSpecs = new Dictionary<SdkLanguage, (string requirement, string[] checkCommand, List<string> instructions)>
206-
{
207-
{ SdkLanguage.Python, ("Python >= 3.14", new[] { "python", "--version" }, new List<string> { "Install Python 3.14 or higher" }) }
208-
};
209-
var languageTestServices = SetupLanguageRequirementsMocks(languageSpecs);
200+
// Arrange - Node.js returns old version
201+
SetupVersionMismatchMock("node", "v18.0.0");
210202

211-
// Act
212203
var tool = new VerifySetupTool(
213204
mockProcessHelper.Object,
214205
logger,
215206
_mockGitHelper.Object,
216-
languageTestServices
207+
languageServices
217208
);
209+
210+
// Act
218211
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path/python");
219212

220-
// Assert
221-
Assert.That(result.Results, Is.Not.Empty);
222-
Assert.That(result.Results.Any(r => r.Requirement.Contains("Python")), Is.True);
213+
// Assert - Should have a failure for Node.js version
214+
Assert.That(result.Results, Is.Not.Null.And.Not.Empty);
215+
Assert.That(result.Results!.Any(r => r.Requirement.Contains("Node")), Is.True);
223216
Assert.That(result.ResponseError, Is.Null);
224217
}
225218

226219
[Test]
227-
public async Task VerifySetup_OnlyChecksSpecifiedLanguages()
220+
public async Task VerifySetup_OnlyChecksLanguageSpecificRequirements_ForSpecifiedLanguage()
228221
{
229-
// Arrange - Set up multiple language specs, but only request python
230-
var languageSpecs = new Dictionary<SdkLanguage, (string, string[], List<string>)>
231-
{
232-
{ SdkLanguage.Python, ("Python >= 3.14", new[] { "python", "--version" }, new List<string> { "Install Python 3.14" }) },
233-
{ SdkLanguage.Java, ("Java >= 17", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) },
234-
{ SdkLanguage.DotNet, (".NET >= 8.0", new[] { "dotnet", "--version" }, new List<string> { "Install .NET 8.0" }) }
235-
};
236-
237-
var languageTestServices = SetupLanguageRequirementsMocks(languageSpecs);
222+
// Arrange - Java command fails, but we only request Python
238223
SetupFailedProcessMock("java", 1, "java: command not found");
239224

240-
// Act
241225
var tool = new VerifySetupTool(
242226
mockProcessHelper.Object,
243227
logger,
244228
_mockGitHelper.Object,
245-
languageTestServices
229+
languageServices
246230
);
231+
232+
// Act - Only request Python
247233
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path/python");
248234

249-
// Assert
250-
Assert.That(result.Results, Is.Not.Empty);
251-
Assert.That(result.Results.Count, Is.EqualTo(1));
252-
Assert.That(result.Results[0].Requirement, Does.Contain("Python"));
235+
// Assert - Should not fail for Java requirements since we only requested Python
236+
Assert.That(result.Results?.Any(r => r.Requirement.Contains("Java")) ?? false, Is.False);
253237
Assert.That(result.ResponseError, Is.Null);
254238
}
255239

256240
[Test]
257-
public async Task VerifySetup_ChecksMultipleSpecifiedLanguages()
241+
public async Task VerifySetup_ChecksCoreRequirements_ForAnyLanguage()
258242
{
259-
// Arrange
260-
var languageSpecs = new Dictionary<SdkLanguage, (string, string[], List<string>)>
261-
{
262-
{ SdkLanguage.Python, ("Python >= 3.8", new[] { "python", "--version" }, new List<string> { "Install Python 3.8" }) },
263-
{ SdkLanguage.Java, ("Java >= 17.0", new[] { "java", "-version" }, new List<string> { "Install Java 17" }) }
264-
};
243+
// Arrange - PowerShell command fails
244+
SetupFailedProcessMock("pwsh", 1, "pwsh: command not found");
265245

266-
var languageTestServices = SetupLanguageRequirementsMocks(languageSpecs);
267-
268-
// Act - Request both Python and Java
269246
var tool = new VerifySetupTool(
270247
mockProcessHelper.Object,
271248
logger,
272249
_mockGitHelper.Object,
273-
languageTestServices
250+
languageServices
274251
);
275-
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python, SdkLanguage.Java }, "/test/path/java");
276252

277-
// Assert
278-
Assert.That(result.ResponseError, Is.Null);
253+
// Act - Request any language
254+
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Go }, "/test/path/go");
255+
256+
// Assert - Should fail for PowerShell since it's a core requirement
257+
Assert.That(result.Results, Is.Not.Null.And.Not.Empty);
258+
Assert.That(result.Results!.Any(r => r.Requirement.Contains("PowerShell")), Is.True);
279259
}
280260

281261
[Test]
282-
public async Task VerifySetup_HandlesInvalidLanguageInput()
262+
public async Task VerifySetup_ReturnsInstructions_WhenRequirementFails()
283263
{
284-
// Act - Pass invalid language
264+
// Arrange - Node.js command fails
265+
SetupFailedProcessMock("node", 1, "node: command not found");
266+
285267
var tool = new VerifySetupTool(
286268
mockProcessHelper.Object,
287269
logger,
288270
_mockGitHelper.Object,
289271
languageServices
290272
);
291-
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { (SdkLanguage)(-1) }, "/test/path");
292273

293-
// Assert - Should succeed with just core requirements
294-
Assert.That(result.ResponseError, Is.Null);
295-
Assert.That(result.Results, Is.Empty);
274+
// Act
275+
var result = await tool.VerifySetup(new HashSet<SdkLanguage> { SdkLanguage.Python }, "/test/path/python");
276+
277+
// Assert - Should include installation instructions
278+
Assert.That(result.Results, Is.Not.Null.And.Not.Empty);
279+
var nodeResult = result.Results!.FirstOrDefault(r => r.Requirement.Contains("Node"));
280+
Assert.That(nodeResult, Is.Not.Null);
281+
Assert.That(nodeResult!.Instructions, Is.Not.Empty);
296282
}
297283

298284
[Test]

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Azure.Sdk.Tools.Cli.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@
6363
<EmbeddedResource Include="Images/quokka.ansi" />
6464
</ItemGroup>
6565

66-
<ItemGroup>
67-
<EmbeddedResource Include="Configuration/RequirementsV1.json" />
68-
</ItemGroup>
69-
7066
<ItemGroup>
7167
<!-- Include changelog and documentation files in the package -->
7268
<None Include="CHANGELOG.md" Pack="true" PackagePath="CHANGELOG.md" />

0 commit comments

Comments
 (0)