Skip to content

Commit 84d40f3

Browse files
Merge pull request #2351 from KathleenDollard/powderhouse-subsystem-fixes-and-directives
Powderhouse directives
2 parents 5ae588a + 06fa079 commit 84d40f3

34 files changed

+1167
-458
lines changed

src/System.CommandLine.Subsystems.Tests/AlternateSubsystems.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.CommandLine.Directives;
45
using System.CommandLine.Subsystems;
6+
using System.CommandLine.Subsystems.Annotations;
57

68
namespace System.CommandLine.Subsystems.Tests
79
{
@@ -45,11 +47,11 @@ internal class VersionWithInitializeAndTeardown : VersionSubsystem
4547
internal bool ExecutionWasRun;
4648
internal bool TeardownWasRun;
4749

48-
protected override CliConfiguration Initialize(CliConfiguration configuration)
50+
protected override CliConfiguration Initialize(InitializationContext context)
4951
{
5052
// marker hack needed because ConsoleHack not available in initialization
5153
InitializationWasRun = true;
52-
return base.Initialize(configuration);
54+
return base.Initialize(context);
5355
}
5456

5557
protected override CliExit Execute(PipelineContext pipelineContext)
@@ -65,5 +67,13 @@ protected override CliExit TearDown(CliExit cliExit)
6567
}
6668
}
6769

70+
internal class StringDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
71+
: DirectiveSubsystem("other",SubsystemKind.Other, annotationProvider)
72+
{ }
73+
74+
internal class BooleanDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
75+
: DirectiveSubsystem("diagram", SubsystemKind.Other, annotationProvider)
76+
{ }
77+
6878
}
6979
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using FluentAssertions;
5+
using System.CommandLine.Directives;
6+
using System.CommandLine.Parsing;
7+
using Xunit;
8+
9+
namespace System.CommandLine.Subsystems.Tests;
10+
11+
public class DiagramSubsystemTests
12+
{
13+
14+
[Theory]
15+
[ClassData(typeof(TestData.Diagram))]
16+
public void Diagram_is_activated_only_when_requested(string input, bool expectedIsActive)
17+
{
18+
CliRootCommand rootCommand = [new CliCommand("x")];
19+
var configuration = new CliConfiguration(rootCommand);
20+
var subsystem = new DiagramSubsystem();
21+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
22+
23+
Subsystem.Initialize(subsystem, configuration, args);
24+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
25+
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);
26+
27+
isActive.Should().Be(expectedIsActive);
28+
}
29+
30+
[Theory]
31+
[ClassData(typeof(TestData.Diagram))]
32+
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(string input, bool expectedIsActive)
33+
{
34+
CliRootCommand rootCommand = [new CliCommand("x")];
35+
var configuration = new CliConfiguration(rootCommand);
36+
var subsystem = new DiagramSubsystem();
37+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
38+
39+
Subsystem.Initialize(subsystem, configuration, args);
40+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
41+
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);
42+
43+
isActive.Should().Be(expectedIsActive);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using FluentAssertions;
5+
using System.CommandLine.Directives;
6+
using System.CommandLine.Parsing;
7+
using Xunit;
8+
9+
namespace System.CommandLine.Subsystems.Tests;
10+
11+
public class DirectiveSubsystemTests
12+
{
13+
14+
// For Boolean tests see DiagramSubsystemTests
15+
16+
[Theory]
17+
[ClassData(typeof(TestData.Directive))]
18+
// TODO: Not sure why these tests are passing
19+
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(
20+
string input, bool expectedBoolIsActive, bool expectedStringIsActive, string? expectedValue)
21+
{
22+
CliRootCommand rootCommand = [new CliCommand("x")];
23+
var configuration = new CliConfiguration(rootCommand);
24+
var stringSubsystem = new AlternateSubsystems.StringDirectiveSubsystem();
25+
var boolSubsystem = new AlternateSubsystems.BooleanDirectiveSubsystem();
26+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
27+
28+
Subsystem.Initialize(stringSubsystem, configuration, args);
29+
Subsystem.Initialize(boolSubsystem, configuration, args);
30+
31+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
32+
var stringIsActive = Subsystem.GetIsActivated(stringSubsystem, parseResult);
33+
var boolIsActive = Subsystem.GetIsActivated(boolSubsystem, parseResult);
34+
var actualValue = stringSubsystem.Value;
35+
36+
boolIsActive.Should().Be(expectedBoolIsActive);
37+
stringIsActive.Should().Be(expectedStringIsActive);
38+
actualValue.Should().Be(expectedValue);
39+
40+
}
41+
}

src/System.CommandLine.Subsystems.Tests/PipelineTests.cs

+86-79
Original file line numberDiff line numberDiff line change
@@ -3,87 +3,78 @@
33

44
using FluentAssertions;
55
using System.CommandLine.Parsing;
6-
using System.Reflection;
76
using Xunit;
87

98
namespace System.CommandLine.Subsystems.Tests
109
{
1110
public class PipelineTests
1211
{
13-
14-
private static readonly string? version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly())
15-
?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
16-
?.InformationalVersion;
17-
12+
private static Pipeline GetTestPipeline(VersionSubsystem versionSubsystem)
13+
=> new()
14+
{
15+
Version = versionSubsystem
16+
};
17+
private static CliConfiguration GetNewTestConfiguration()
18+
=> new(new CliRootCommand { new CliOption<bool>("-x") }); // Add option expected by test data
19+
20+
private static ConsoleHack GetNewTestConsole()
21+
=> new ConsoleHack().RedirectToBuffer(true);
22+
23+
//private static (Pipeline pipeline, CliConfiguration configuration, ConsoleHack consoleHack) StandardObjects(VersionSubsystem versionSubsystem)
24+
//{
25+
// var configuration = new CliConfiguration(new CliRootCommand { new CliOption<bool>("-x") });
26+
// var pipeline = new Pipeline
27+
// {
28+
// Version = versionSubsystem
29+
// };
30+
// var consoleHack = new ConsoleHack().RedirectToBuffer(true);
31+
// return (pipeline, configuration, consoleHack);
32+
//}
1833

1934
[Theory]
20-
[InlineData("-v", true)]
21-
[InlineData("--version", true)]
22-
[InlineData("-x", false)]
23-
[InlineData("", false)]
24-
[InlineData(null, false)]
35+
[ClassData(typeof(TestData.Version))]
2536
public void Subsystem_runs_in_pipeline_only_when_requested(string input, bool shouldRun)
2637
{
27-
var configuration = new CliConfiguration(new CliRootCommand { });
28-
var pipeline = new Pipeline
29-
{
30-
Version = new VersionSubsystem()
31-
};
32-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
38+
var pipeline = GetTestPipeline(new VersionSubsystem());
39+
var console = GetNewTestConsole();
3340

34-
var exit = pipeline.Execute(configuration, input, consoleHack);
41+
var exit = pipeline.Execute(GetNewTestConfiguration(), input, console);
3542

3643
exit.ExitCode.Should().Be(0);
3744
exit.Handled.Should().Be(shouldRun);
3845
if (shouldRun)
3946
{
40-
consoleHack.GetBuffer().Trim().Should().Be(version);
47+
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
4148
}
4249
}
4350

4451
[Theory]
45-
[InlineData("-v", true)]
46-
[InlineData("--version", true)]
47-
[InlineData("-x", false)]
48-
[InlineData("", false)]
49-
[InlineData(null, false)]
52+
[ClassData(typeof(TestData.Version))]
5053
public void Subsystem_runs_with_explicit_parse_only_when_requested(string input, bool shouldRun)
5154
{
52-
var configuration = new CliConfiguration(new CliRootCommand { });
53-
var pipeline = new Pipeline
54-
{
55-
Version = new VersionSubsystem()
56-
};
57-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
55+
var pipeline = GetTestPipeline(new VersionSubsystem());
56+
var console = GetNewTestConsole();
5857

59-
var result = pipeline.Parse(configuration, input);
60-
var exit = pipeline.Execute(result, input, consoleHack);
58+
var result = pipeline.Parse(GetNewTestConfiguration(), input);
59+
var exit = pipeline.Execute(result, input, console);
6160

6261
exit.ExitCode.Should().Be(0);
6362
exit.Handled.Should().Be(shouldRun);
6463
if (shouldRun)
6564
{
66-
consoleHack.GetBuffer().Trim().Should().Be(version);
65+
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
6766
}
6867
}
6968

7069
[Theory]
71-
[InlineData("-v", true)]
72-
[InlineData("--version", true)]
73-
[InlineData("-x", false)]
74-
[InlineData("", false)]
75-
[InlineData(null, false)]
70+
[ClassData(typeof(TestData.Version))]
7671
public void Subsystem_runs_initialize_and_teardown_when_requested(string input, bool shouldRun)
7772
{
78-
var configuration = new CliConfiguration(new CliRootCommand { });
79-
AlternateSubsystems.VersionWithInitializeAndTeardown versionSubsystem = new AlternateSubsystems.VersionWithInitializeAndTeardown();
80-
var pipeline = new Pipeline
81-
{
82-
Version = versionSubsystem
83-
};
84-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
73+
var versionSubsystem = new AlternateSubsystems.VersionWithInitializeAndTeardown();
74+
var pipeline = GetTestPipeline(versionSubsystem);
75+
var console = GetNewTestConsole();
8576

86-
var exit = pipeline.Execute(configuration, input, consoleHack);
77+
var exit = pipeline.Execute(GetNewTestConfiguration(), input, console);
8778

8879
exit.ExitCode.Should().Be(0);
8980
exit.Handled.Should().Be(shouldRun);
@@ -94,56 +85,72 @@ public void Subsystem_runs_initialize_and_teardown_when_requested(string input,
9485

9586

9687
[Theory]
97-
[InlineData("-v", true)]
98-
[InlineData("--version", true)]
99-
[InlineData("-x", false)]
100-
[InlineData("", false)]
101-
[InlineData(null, false)]
102-
public void Subsystem_can_be_used_without_runner(string input, bool shouldRun)
88+
[ClassData(typeof(TestData.Version))]
89+
public void Subsystem_works_without_pipeline(string input, bool shouldRun)
10390
{
104-
var configuration = new CliConfiguration(new CliRootCommand { });
10591
var versionSubsystem = new VersionSubsystem();
106-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
107-
108-
Subsystem.Initialize(versionSubsystem, configuration);
109-
// TODO: I do not know why anyone would do this, but I do not see a reason to work to block it. See style2 below
110-
var parseResult = CliParser.Parse(configuration.RootCommand, input, configuration);
92+
// TODO: Ensure an efficient conversion as people may copy this code
93+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
94+
var console = GetNewTestConsole();
95+
var configuration = GetNewTestConfiguration();
96+
97+
Subsystem.Initialize(versionSubsystem, configuration, args);
98+
// This approach might be taken if someone is using a subsystem just for initialization
99+
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
111100
bool value = parseResult.GetValue<bool>("--version");
112101

102+
parseResult.Errors.Should().BeEmpty();
113103
value.Should().Be(shouldRun);
114-
if (shouldRun)
104+
if (shouldRun)
115105
{
116106
// TODO: Add an execute overload to avoid checking activated twice
117-
var exit = Subsystem.Execute(versionSubsystem, parseResult, input, consoleHack);
107+
var exit = Subsystem.Execute(versionSubsystem, parseResult, input, console);
118108
exit.Should().NotBeNull();
119109
exit.ExitCode.Should().Be(0);
120110
exit.Handled.Should().BeTrue();
121-
consoleHack.GetBuffer().Trim().Should().Be(version);
111+
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
122112
}
123113
}
124114

125115
[Theory]
126-
[InlineData("-v", true)]
127-
[InlineData("--version", true)]
128-
[InlineData("-x", false)]
129-
[InlineData("", false)]
130-
[InlineData(null, false)]
131-
public void Subsystem_can_be_used_without_runner_style2(string input, bool shouldRun)
116+
[ClassData(typeof(TestData.Version))]
117+
public void Subsystem_works_without_pipeline_style2(string input, bool shouldRun)
132118
{
133-
var configuration = new CliConfiguration(new CliRootCommand { });
134119
var versionSubsystem = new VersionSubsystem();
135-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
120+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
121+
var console = GetNewTestConsole();
122+
var configuration = GetNewTestConfiguration();
136123
var expectedVersion = shouldRun
137-
? version
124+
? TestData.AssemblyVersionString
138125
: "";
139126

140-
Subsystem.Initialize(versionSubsystem, configuration);
141-
var parseResult = CliParser.Parse(configuration.RootCommand, input, configuration);
142-
var exit = Subsystem.ExecuteIfNeeded(versionSubsystem, parseResult, input, consoleHack);
127+
// Someone might use this approach if they wanted to do something with the ParseResult
128+
Subsystem.Initialize(versionSubsystem, configuration, args);
129+
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
130+
var exit = Subsystem.ExecuteIfNeeded(versionSubsystem, parseResult, input, console);
143131

144132
exit.ExitCode.Should().Be(0);
145133
exit.Handled.Should().Be(shouldRun);
146-
consoleHack.GetBuffer().Trim().Should().Be(expectedVersion);
134+
console.GetBuffer().Trim().Should().Be(expectedVersion);
135+
}
136+
137+
138+
[Theory]
139+
[InlineData("-xy", false)]
140+
[InlineData("--versionx", false)]
141+
public void Subsystem_runs_when_requested_even_when_there_are_errors(string input, bool shouldRun)
142+
{
143+
var versionSubsystem = new VersionSubsystem();
144+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
145+
var configuration = GetNewTestConfiguration();
146+
147+
Subsystem.Initialize(versionSubsystem, configuration, args);
148+
// This approach might be taken if someone is using a subsystem just for initialization
149+
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
150+
bool value = parseResult.GetValue<bool>("--version");
151+
152+
parseResult.Errors.Should().NotBeEmpty();
153+
value.Should().Be(shouldRun);
147154
}
148155

149156
[Fact]
@@ -171,9 +178,8 @@ public void Normal_pipeline_contains_no_subsystems()
171178
public void Subsystems_can_access_each_others_data()
172179
{
173180
// TODO: Explore a mechanism that doesn't require the reference to retrieve data, this shows that it is awkward
174-
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
175181
var symbol = new CliOption<bool>("-x");
176-
182+
var console = GetNewTestConsole();
177183
var pipeline = new StandardPipeline
178184
{
179185
Version = new AlternateSubsystems.VersionThatUsesHelpData(symbol)
@@ -183,9 +189,10 @@ public void Subsystems_can_access_each_others_data()
183189
{
184190
symbol.With(pipeline.Help.Description, "Testing")
185191
};
186-
pipeline.Execute(new CliConfiguration(rootCommand), "-v", consoleHack);
187-
consoleHack.GetBuffer().Trim().Should().Be($"Testing");
188-
}
189192

193+
pipeline.Execute(new CliConfiguration(rootCommand), "-v", console);
194+
195+
console.GetBuffer().Trim().Should().Be($"Testing");
196+
}
190197
}
191198
}

0 commit comments

Comments
 (0)