Skip to content

Commit ae2e197

Browse files
Clean up duplicate options in dotnet test (#50524)
1 parent 115cbcf commit ae2e197

File tree

2 files changed

+68
-11
lines changed

2 files changed

+68
-11
lines changed

src/Cli/dotnet/Commands/Test/TestingPlatformCommand.Help.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public IEnumerable<Action<HelpContext>> CustomHelpLayout()
2929
return;
3030
}
3131

32-
Dictionary<bool, List<CommandLineOption>> allOptions = GetAllOptions();
32+
Dictionary<bool, List<CommandLineOption>> allOptions = GetAllOptions(context.Command.Options);
3333
allOptions.TryGetValue(true, out List<CommandLineOption> builtInOptions);
3434
allOptions.TryGetValue(false, out List<CommandLineOption> nonBuiltInOptions);
3535

@@ -128,29 +128,36 @@ private void OnHelpRequested(object sender, HelpEventArgs args)
128128
(isBuiltIn, value) => [.. value, (moduleName, nonBuiltInOptions.ToArray())]);
129129
}
130130

131-
private Dictionary<bool, List<CommandLineOption>> GetAllOptions()
131+
private Dictionary<bool, List<CommandLineOption>> GetAllOptions(IList<Option> commandOptions)
132132
{
133-
Dictionary<bool, List<CommandLineOption>> builtInToOptions = [];
133+
Dictionary<bool, List<CommandLineOption>> filteredOptions = [];
134+
135+
// Create a set of option names from the command's options for efficient lookup
136+
var commandOptionNames = commandOptions.Select(o => o.Name.TrimStart('-')).ToHashSet(StringComparer.OrdinalIgnoreCase);
134137

135138
foreach (KeyValuePair<string, CommandLineOption> option in _commandLineOptionNameToModuleNames)
136139
{
137-
if (!builtInToOptions.TryGetValue(option.Value.IsBuiltIn.Value, out List<CommandLineOption> value))
138-
{
139-
builtInToOptions.Add(option.Value.IsBuiltIn.Value, [option.Value]);
140-
}
141-
else
140+
// Only include options that are NOT already present in the command's options
141+
if (!commandOptionNames.Contains(option.Value.Name))
142142
{
143-
value.Add(option.Value);
143+
if (!filteredOptions.TryGetValue(option.Value.IsBuiltIn.Value, out List<CommandLineOption> value))
144+
{
145+
filteredOptions.Add(option.Value.IsBuiltIn.Value, [option.Value]);
146+
}
147+
else
148+
{
149+
value.Add(option.Value);
150+
}
144151
}
145152
}
146153

147154
// Sort options alphabetically by name
148-
foreach (var optionsList in builtInToOptions.Values)
155+
foreach (var optionsList in filteredOptions.Values)
149156
{
150157
optionsList.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase));
151158
}
152159

153-
return builtInToOptions;
160+
return filteredOptions;
154161
}
155162

156163
private static Dictionary<bool, List<(string[], string[])>> GetModulesToMissingOptions(

test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsHelp.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,55 @@ public void RunHelpOnMultipleTestProjects_ShouldReturnExitCodeSuccess(string con
6262

6363
result.ExitCode.Should().Be(ExitCodes.Success);
6464
}
65+
66+
[InlineData(TestingConstants.Debug)]
67+
[InlineData(TestingConstants.Release)]
68+
[Theory]
69+
public void RunHelpCommand_ShouldNotShowDuplicateOptions(string configuration)
70+
{
71+
TestAsset testInstance = _testAssetsManager.CopyTestAsset("TestProjectSolutionWithTestsAndArtifacts", Guid.NewGuid().ToString()).WithSource();
72+
73+
CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
74+
.WithWorkingDirectory(testInstance.Path)
75+
.Execute(TestingPlatformOptions.HelpOption.Name, TestingPlatformOptions.ConfigurationOption.Name, configuration);
76+
77+
// Parse the help output to extract option names
78+
var helpOutput = result.StdOut;
79+
80+
// Check for specific options we care about
81+
string outputOptionName = TestingPlatformOptions.OutputOption.Name; // --output
82+
string noAnsiOptionName = TestingPlatformOptions.NoAnsiOption.Name; // --no-ansi
83+
84+
// Count occurrences of each option in the help output
85+
int outputOptionCount = CountOptionOccurrences(helpOutput!, outputOptionName);
86+
int noAnsiOptionCount = CountOptionOccurrences(helpOutput!, noAnsiOptionName);
87+
88+
// Assert that each option appears only once
89+
outputOptionCount.Should().Be(1, $"Option '{outputOptionName}' should not appear more than once in help output");
90+
noAnsiOptionCount.Should().Be(1, $"Option '{noAnsiOptionName}' should not appear more than once in help output");
91+
92+
result.ExitCode.Should().Be(ExitCodes.Success);
93+
}
94+
95+
private static int CountOptionOccurrences(string helpOutput, string optionName)
96+
{
97+
// Split by lines and look for lines that start with the option (accounting for indentation)
98+
var lines = helpOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
99+
int count = 0;
100+
101+
foreach (var line in lines)
102+
{
103+
var trimmedLine = line.Trim();
104+
// Look for lines that start with the option name (e.g., "--output" or "--no-ansi")
105+
if (trimmedLine.StartsWith(optionName, StringComparison.OrdinalIgnoreCase))
106+
{
107+
count++;
108+
}
109+
}
110+
111+
return count;
112+
}
113+
114+
65115
}
66116
}

0 commit comments

Comments
 (0)