Skip to content

Commit 9bb4b9c

Browse files
committed
Refactor dotnet tool aliases and add end-to-end coverage
- split DotNetAliases.ToolCommands.cs into command-specific DotNetAliases.Tool.* files - keep forwarded tool arguments as ProcessArgumentBuilder parameters for exec/run overloads - suppress --verbosity for tool subcommands that do not support it (run/list/search/uninstall) - update dotnet tool unit tests for the new argument rendering and customization restoration behavior - add integration coverage for local dotnet tool install -> list -> uninstall using a manifest
1 parent e68c3a7 commit 9bb4b9c

12 files changed

Lines changed: 1307 additions & 982 deletions

src/Cake.Common.Tests/Unit/Tools/DotNet/Tool/DotNetToolCommandTests.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@ public static IEnumerable<object[]> DotNetToolCommandCases()
3636
{
3737
yield return new object[] { nameof(DotNetToolCommandInvocation.ExecuteProjectPackageWithArgumentsAndSettings), "dotnetsay@1.2.3", "\"tool\" exec dotnetsay@1.2.3 --verbosity minimal -- --tool-option tool-value", true };
3838
yield return new object[] { nameof(DotNetToolCommandInvocation.InstallFull), "dotnetsay", "\"tool\" install dotnetsay --global --verbosity minimal", true };
39-
yield return new object[] { nameof(DotNetToolCommandInvocation.ListFull), "dotnetsay", "\"tool\" list dotnetsay --global --verbosity minimal", true };
40-
yield return new object[] { nameof(DotNetToolCommandInvocation.ListSettings), null, "\"tool\" list --global --verbosity minimal", false };
41-
yield return new object[] { nameof(DotNetToolCommandInvocation.ListPackageSettings), "dotnetsay", "\"tool\" list dotnetsay --global --verbosity minimal", false };
39+
yield return new object[] { nameof(DotNetToolCommandInvocation.ListFull), "dotnetsay", "\"tool\" list dotnetsay --global", true };
40+
yield return new object[] { nameof(DotNetToolCommandInvocation.ListSettings), null, "\"tool\" list --global", false };
41+
yield return new object[] { nameof(DotNetToolCommandInvocation.ListPackageSettings), "dotnetsay", "\"tool\" list dotnetsay --global", false };
4242
yield return new object[] { nameof(DotNetToolCommandInvocation.RestoreFull), null, "\"tool\" restore --disable-parallel --verbosity minimal", true };
4343
yield return new object[] { nameof(DotNetToolCommandInvocation.RestoreSettings), null, "\"tool\" restore --disable-parallel --verbosity minimal", false };
44-
yield return new object[] { nameof(DotNetToolCommandInvocation.RunFull), "dotnetsay", "\"tool\" run dotnetsay --verbosity minimal -- Hello --name World", true };
44+
yield return new object[] { nameof(DotNetToolCommandInvocation.RunFull), "dotnetsay", "\"tool\" run dotnetsay -- Hello --name World", true };
4545
yield return new object[] { nameof(DotNetToolCommandInvocation.RunProjectCommandArguments), "dotnetsay", "\"tool\" run dotnetsay -- Hello --name World", true };
4646
yield return new object[] { nameof(DotNetToolCommandInvocation.RunCommandArguments), "dotnetsay", "\"tool\" run dotnetsay -- Hello --name World", false };
47-
yield return new object[] { nameof(DotNetToolCommandInvocation.RunCommandArgumentsAndSettings), "dotnetsay", "\"tool\" run dotnetsay --verbosity minimal -- Hello --name World", false };
48-
yield return new object[] { nameof(DotNetToolCommandInvocation.RunCommandSettings), "dotnetsay", "\"tool\" run dotnetsay --verbosity minimal", false };
49-
yield return new object[] { nameof(DotNetToolCommandInvocation.SearchFull), "cake", "\"tool\" search cake --detail --verbosity minimal", true };
50-
yield return new object[] { nameof(DotNetToolCommandInvocation.UninstallFull), "dotnetsay", "\"tool\" uninstall dotnetsay --global --verbosity minimal", true };
47+
yield return new object[] { nameof(DotNetToolCommandInvocation.RunCommandArgumentsAndSettings), "dotnetsay", "\"tool\" run dotnetsay -- Hello --name World", false };
48+
yield return new object[] { nameof(DotNetToolCommandInvocation.RunCommandSettings), "dotnetsay", "\"tool\" run dotnetsay", false };
49+
yield return new object[] { nameof(DotNetToolCommandInvocation.SearchFull), "cake", "\"tool\" search cake --detail", true };
50+
yield return new object[] { nameof(DotNetToolCommandInvocation.UninstallFull), "dotnetsay", "\"tool\" uninstall dotnetsay --global", true };
5151
yield return new object[] { nameof(DotNetToolCommandInvocation.UpdateFull), "dotnetsay", "\"tool\" update dotnetsay --global --verbosity minimal", true };
5252
}
5353

@@ -337,6 +337,27 @@ public void DotNetToolRun_RenderCommandSpecificSettings()
337337
Assert.Equal(expected, result.Args);
338338
}
339339

340+
[Fact]
341+
public void DotNetToolRun_WithArgumentCustomization_RestoresOriginalCustomization()
342+
{
343+
// Given
344+
Func<ProcessArgumentBuilder, ProcessArgumentBuilder> addInteractive = arguments => arguments.Append("--interactive");
345+
var fixture = new DotNetToolCommandFixture
346+
{
347+
Invocation = DotNetToolCommandInvocation.RunCommandArgumentsAndSettings,
348+
CommandArgument = "dotnetsay"
349+
};
350+
fixture.RunArguments = Arguments("--tool-option");
351+
fixture.RunSettings.ArgumentCustomization = addInteractive;
352+
353+
// When
354+
var result = fixture.Run();
355+
356+
// Then
357+
Assert.Equal("\"tool\" run dotnetsay --interactive -- --tool-option", result.Args);
358+
Assert.Same(addInteractive, fixture.RunSettings.ArgumentCustomization);
359+
}
360+
340361
[Fact]
341362
public void DotNetToolSearch_RenderCommandSpecificSettings()
342363
{
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Cake.Common.Tools.DotNet.Tool;
6+
using Cake.Core;
7+
using Cake.Core.IO;
8+
using System;
9+
using System.Collections.Generic;
10+
11+
namespace Cake.Common.Tools.DotNet
12+
{
13+
/// <summary>
14+
/// <para>Contains functionality related to <see href="https://github.com/dotnet/cli">.NET CLI</see>.</para>
15+
/// <para>
16+
/// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where
17+
/// the Cake script is being executed. See this <see href="https://www.microsoft.com/net/core">page</see> for information
18+
/// on how to install.
19+
/// </para>
20+
/// </summary>
21+
public static partial class DotNetAliases
22+
{
23+
private static void RunDotNetToolCommand(
24+
ICakeContext context,
25+
FilePath projectPath,
26+
ProcessArgumentBuilder arguments,
27+
DotNetToolSettings settings)
28+
{
29+
ArgumentNullException.ThrowIfNull(context);
30+
ArgumentNullException.ThrowIfNull(arguments);
31+
ArgumentNullException.ThrowIfNull(settings);
32+
33+
context.DotNetTool(projectPath, "tool", arguments, settings);
34+
}
35+
36+
private static void RunDotNetToolCommandWithoutVerbosity(
37+
ICakeContext context,
38+
FilePath projectPath,
39+
ProcessArgumentBuilder arguments,
40+
DotNetToolSettings settings)
41+
{
42+
ArgumentNullException.ThrowIfNull(context);
43+
ArgumentNullException.ThrowIfNull(arguments);
44+
ArgumentNullException.ThrowIfNull(settings);
45+
46+
var verbosity = settings.Verbosity;
47+
settings.Verbosity = null;
48+
49+
try
50+
{
51+
context.DotNetTool(projectPath, "tool", arguments, settings);
52+
}
53+
finally
54+
{
55+
settings.Verbosity = verbosity;
56+
}
57+
}
58+
59+
private static void RunDotNetToolCommandWithForwardedArguments(
60+
ICakeContext context,
61+
FilePath projectPath,
62+
ProcessArgumentBuilder arguments,
63+
ProcessArgumentBuilder forwardedArguments,
64+
DotNetToolSettings settings)
65+
{
66+
ArgumentNullException.ThrowIfNull(context);
67+
ArgumentNullException.ThrowIfNull(arguments);
68+
ArgumentNullException.ThrowIfNull(settings);
69+
70+
if (forwardedArguments.IsNullOrEmpty())
71+
{
72+
context.DotNetTool(projectPath, "tool", arguments, settings);
73+
return;
74+
}
75+
76+
var argumentCustomization = settings.ArgumentCustomization;
77+
settings.ArgumentCustomization = dotnetArguments =>
78+
{
79+
var customizedArguments = argumentCustomization?.Invoke(dotnetArguments) ?? dotnetArguments;
80+
customizedArguments.Append("--");
81+
forwardedArguments.CopyTo(customizedArguments);
82+
return customizedArguments;
83+
};
84+
85+
try
86+
{
87+
context.DotNetTool(projectPath, "tool", arguments, settings);
88+
}
89+
finally
90+
{
91+
settings.ArgumentCustomization = argumentCustomization;
92+
}
93+
}
94+
95+
private static void RunDotNetToolCommandWithForwardedArgumentsWithoutVerbosity(
96+
ICakeContext context,
97+
FilePath projectPath,
98+
ProcessArgumentBuilder arguments,
99+
ProcessArgumentBuilder forwardedArguments,
100+
DotNetToolSettings settings)
101+
{
102+
ArgumentNullException.ThrowIfNull(context);
103+
ArgumentNullException.ThrowIfNull(arguments);
104+
ArgumentNullException.ThrowIfNull(settings);
105+
106+
var verbosity = settings.Verbosity;
107+
settings.Verbosity = null;
108+
109+
try
110+
{
111+
RunDotNetToolCommandWithForwardedArguments(context, projectPath, arguments, forwardedArguments, settings);
112+
}
113+
finally
114+
{
115+
settings.Verbosity = verbosity;
116+
}
117+
}
118+
119+
private static void AppendToolExecuteSettings(ProcessArgumentBuilder builder, DotNetToolExecuteSettings settings, ICakeEnvironment environment)
120+
{
121+
AppendPackageResolutionSettings(builder, settings.Version, settings.ConfigFile, settings.Source, settings.AddSource, settings.Prerelease, environment);
122+
AppendSwitch(builder, "--allow-roll-forward", settings.AllowRollForward);
123+
AppendRestoreSettings(builder, settings.DisableParallel, settings.IgnoreFailedSources, settings.NoHttpCache, settings.Interactive);
124+
}
125+
126+
private static void AppendToolInstallSettings(ProcessArgumentBuilder builder, DotNetToolInstallSettings settings, ICakeEnvironment environment)
127+
{
128+
AppendSwitch(builder, "--global", settings.Global);
129+
AppendSwitch(builder, "--local", settings.Local);
130+
AppendDirectoryPath(builder, "--tool-path", settings.ToolInstallationPath, environment);
131+
AppendPackageResolutionSettings(builder, settings.Version, settings.ConfigFile, settings.Source, settings.AddSource, settings.Prerelease, environment);
132+
AppendFilePath(builder, "--tool-manifest", settings.ToolManifest, environment);
133+
AppendString(builder, "--framework", settings.Framework);
134+
AppendRestoreSettings(builder, settings.DisableParallel, settings.IgnoreFailedSources, settings.NoHttpCache, settings.Interactive);
135+
AppendSwitch(builder, "--allow-downgrade", settings.AllowDowngrade);
136+
AppendString(builder, "--arch", settings.Architecture);
137+
AppendSwitch(builder, "--create-manifest-if-needed", settings.CreateManifestIfNeeded);
138+
AppendSwitch(builder, "--allow-roll-forward", settings.AllowRollForward);
139+
}
140+
141+
private static void AppendToolListSettings(ProcessArgumentBuilder builder, DotNetToolListSettings settings, ICakeEnvironment environment)
142+
{
143+
AppendSwitch(builder, "--global", settings.Global);
144+
AppendSwitch(builder, "--local", settings.Local);
145+
AppendDirectoryPath(builder, "--tool-path", settings.ToolInstallationPath, environment);
146+
147+
if (settings.Format.HasValue)
148+
{
149+
builder.AppendSwitch("--format", settings.Format.Value == DotNetToolListFormat.Json ? "json" : "table");
150+
}
151+
}
152+
153+
private static void AppendToolRestoreSettings(ProcessArgumentBuilder builder, DotNetToolRestoreSettings settings, ICakeEnvironment environment)
154+
{
155+
AppendFilePath(builder, "--configfile", settings.ConfigFile, environment);
156+
AppendSources(builder, "--add-source", settings.AddSource);
157+
AppendFilePath(builder, "--tool-manifest", settings.ToolManifest, environment);
158+
AppendRestoreSettings(builder, settings.DisableParallel, settings.IgnoreFailedSources, settings.NoHttpCache, settings.Interactive);
159+
}
160+
161+
private static void AppendToolRunSettings(ProcessArgumentBuilder builder, DotNetToolRunSettings settings)
162+
{
163+
AppendSwitch(builder, "--allow-roll-forward", settings.AllowRollForward);
164+
}
165+
166+
private static void AppendToolSearchSettings(ProcessArgumentBuilder builder, DotNetToolSearchSettings settings)
167+
{
168+
AppendSwitch(builder, "--detail", settings.Detail);
169+
170+
if (settings.Skip.HasValue)
171+
{
172+
builder.AppendSwitch("--skip", settings.Skip.Value.ToString());
173+
}
174+
175+
if (settings.Take.HasValue)
176+
{
177+
builder.AppendSwitch("--take", settings.Take.Value.ToString());
178+
}
179+
180+
AppendSwitch(builder, "--prerelease", settings.Prerelease);
181+
}
182+
183+
private static void AppendToolUninstallSettings(ProcessArgumentBuilder builder, DotNetToolUninstallSettings settings, ICakeEnvironment environment)
184+
{
185+
AppendSwitch(builder, "--global", settings.Global);
186+
AppendSwitch(builder, "--local", settings.Local);
187+
AppendDirectoryPath(builder, "--tool-path", settings.ToolInstallationPath, environment);
188+
AppendFilePath(builder, "--tool-manifest", settings.ToolManifest, environment);
189+
}
190+
191+
private static void AppendToolUpdateSettings(ProcessArgumentBuilder builder, DotNetToolUpdateSettings settings, ICakeEnvironment environment)
192+
{
193+
AppendSwitch(builder, "--global", settings.Global);
194+
AppendSwitch(builder, "--local", settings.Local);
195+
AppendDirectoryPath(builder, "--tool-path", settings.ToolInstallationPath, environment);
196+
AppendPackageResolutionSettings(builder, settings.Version, settings.ConfigFile, settings.Source, settings.AddSource, settings.Prerelease, environment);
197+
AppendFilePath(builder, "--tool-manifest", settings.ToolManifest, environment);
198+
AppendString(builder, "--framework", settings.Framework);
199+
AppendRestoreSettings(builder, settings.DisableParallel, settings.IgnoreFailedSources, settings.NoHttpCache, settings.Interactive);
200+
AppendSwitch(builder, "--allow-downgrade", settings.AllowDowngrade);
201+
AppendSwitch(builder, "--all", settings.All);
202+
}
203+
204+
private static void AppendPackageResolutionSettings(
205+
ProcessArgumentBuilder builder,
206+
string version,
207+
FilePath configFile,
208+
ICollection<string> source,
209+
ICollection<string> addSource,
210+
bool prerelease,
211+
ICakeEnvironment environment)
212+
{
213+
AppendString(builder, "--version", version);
214+
AppendFilePath(builder, "--configfile", configFile, environment);
215+
AppendSources(builder, "--source", source);
216+
AppendSources(builder, "--add-source", addSource);
217+
AppendSwitch(builder, "--prerelease", prerelease);
218+
}
219+
220+
private static void AppendRestoreSettings(ProcessArgumentBuilder builder, bool disableParallel, bool ignoreFailedSources, bool noHttpCache, bool interactive)
221+
{
222+
AppendSwitch(builder, "--disable-parallel", disableParallel);
223+
AppendSwitch(builder, "--ignore-failed-sources", ignoreFailedSources);
224+
AppendSwitch(builder, "--no-http-cache", noHttpCache);
225+
AppendSwitch(builder, "--interactive", interactive);
226+
}
227+
228+
private static void AppendRequiredArgument(ProcessArgumentBuilder builder, string argument, string parameterName)
229+
{
230+
if (string.IsNullOrWhiteSpace(argument))
231+
{
232+
throw new ArgumentNullException(parameterName);
233+
}
234+
235+
builder.Append(argument);
236+
}
237+
238+
private static void AppendOptionalArgument(ProcessArgumentBuilder builder, string argument)
239+
{
240+
if (!string.IsNullOrWhiteSpace(argument))
241+
{
242+
builder.Append(argument);
243+
}
244+
}
245+
246+
private static void AppendSwitch(ProcessArgumentBuilder builder, string switchName, bool value)
247+
{
248+
if (value)
249+
{
250+
builder.Append(switchName);
251+
}
252+
}
253+
254+
private static void AppendString(ProcessArgumentBuilder builder, string switchName, string value)
255+
{
256+
if (!string.IsNullOrWhiteSpace(value))
257+
{
258+
builder.AppendSwitchQuoted(switchName, value);
259+
}
260+
}
261+
262+
private static void AppendFilePath(ProcessArgumentBuilder builder, string switchName, FilePath value, ICakeEnvironment environment)
263+
{
264+
if (value != null)
265+
{
266+
builder.AppendSwitchQuoted(switchName, value.MakeAbsolute(environment).FullPath);
267+
}
268+
}
269+
270+
private static void AppendDirectoryPath(ProcessArgumentBuilder builder, string switchName, DirectoryPath value, ICakeEnvironment environment)
271+
{
272+
if (value != null)
273+
{
274+
builder.AppendSwitchQuoted(switchName, value.MakeAbsolute(environment).FullPath);
275+
}
276+
}
277+
278+
private static void AppendSources(ProcessArgumentBuilder builder, string switchName, ICollection<string> sources)
279+
{
280+
if (sources == null)
281+
{
282+
return;
283+
}
284+
285+
foreach (var source in sources)
286+
{
287+
AppendString(builder, switchName, source);
288+
}
289+
}
290+
}
291+
}

0 commit comments

Comments
 (0)