Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Powderhouse subsystems and directives #2351

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions src/System.CommandLine.Subsystems.Tests/AlternateSubsystems.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Directives;
using System.CommandLine.Subsystems;
using System.CommandLine.Subsystems.Annotations;

namespace System.CommandLine.Subsystems.Tests
{
Expand Down Expand Up @@ -45,11 +47,11 @@ internal class VersionWithInitializeAndTeardown : VersionSubsystem
internal bool ExecutionWasRun;
internal bool TeardownWasRun;

protected override CliConfiguration Initialize(CliConfiguration configuration)
protected override CliConfiguration Initialize(InitializationContext context)
{
// marker hack needed because ConsoleHack not available in initialization
InitializationWasRun = true;
return base.Initialize(configuration);
return base.Initialize(context);
}

protected override CliExit Execute(PipelineContext pipelineContext)
Expand All @@ -65,5 +67,13 @@ protected override CliExit TearDown(CliExit cliExit)
}
}

internal class StringDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
: DirectiveSubsystem("other",SubsystemKind.Other, annotationProvider)
{ }

internal class BooleanDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
: DirectiveSubsystem("diagram", SubsystemKind.Other, annotationProvider)
{ }

}
}
45 changes: 45 additions & 0 deletions src/System.CommandLine.Subsystems.Tests/DiagramSubsystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentAssertions;
using System.CommandLine.Directives;
using System.CommandLine.Parsing;
using Xunit;

namespace System.CommandLine.Subsystems.Tests;

public class DiagramSubsystemTests
{

[Theory]
[ClassData(typeof(TestData.Diagram))]
public void Diagram_is_activated_only_when_requested(string input, bool expectedIsActive)
{
CliRootCommand rootCommand = [new CliCommand("x")];
var configuration = new CliConfiguration(rootCommand);
var subsystem = new DiagramSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();

Subsystem.Initialize(subsystem, configuration, args);
var parseResult = CliParser.Parse(rootCommand, input, configuration);
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);

isActive.Should().Be(expectedIsActive);
}

[Theory]
[ClassData(typeof(TestData.Diagram))]
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(string input, bool expectedIsActive)
{
CliRootCommand rootCommand = [new CliCommand("x")];
var configuration = new CliConfiguration(rootCommand);
var subsystem = new DiagramSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();

Subsystem.Initialize(subsystem, configuration, args);
var parseResult = CliParser.Parse(rootCommand, input, configuration);
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);

isActive.Should().Be(expectedIsActive);
}
}
41 changes: 41 additions & 0 deletions src/System.CommandLine.Subsystems.Tests/DirectiveSubsystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentAssertions;
using System.CommandLine.Directives;
using System.CommandLine.Parsing;
using Xunit;

namespace System.CommandLine.Subsystems.Tests;

public class DirectiveSubsystemTests
{

// For Boolean tests see DiagramSubsystemTests

[Theory]
[ClassData(typeof(TestData.Directive))]
// TODO: Not sure why these tests are passing
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(
string input, bool expectedBoolIsActive, bool expectedStringIsActive, string? expectedValue)
{
CliRootCommand rootCommand = [new CliCommand("x")];
var configuration = new CliConfiguration(rootCommand);
var stringSubsystem = new AlternateSubsystems.StringDirectiveSubsystem();
var boolSubsystem = new AlternateSubsystems.BooleanDirectiveSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();

Subsystem.Initialize(stringSubsystem, configuration, args);
Subsystem.Initialize(boolSubsystem, configuration, args);

var parseResult = CliParser.Parse(rootCommand, input, configuration);
var stringIsActive = Subsystem.GetIsActivated(stringSubsystem, parseResult);
var boolIsActive = Subsystem.GetIsActivated(boolSubsystem, parseResult);
var actualValue = stringSubsystem.Value;

boolIsActive.Should().Be(expectedBoolIsActive);
stringIsActive.Should().Be(expectedStringIsActive);
actualValue.Should().Be(expectedValue);

}
}
165 changes: 86 additions & 79 deletions src/System.CommandLine.Subsystems.Tests/PipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,78 @@

using FluentAssertions;
using System.CommandLine.Parsing;
using System.Reflection;
using Xunit;

namespace System.CommandLine.Subsystems.Tests
{
public class PipelineTests
{

private static readonly string? version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly())
?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion;

private static Pipeline GetTestPipeline(VersionSubsystem versionSubsystem)
=> new()
{
Version = versionSubsystem
};
private static CliConfiguration GetNewTestConfiguration()
=> new(new CliRootCommand { new CliOption<bool>("-x") }); // Add option expected by test data

private static ConsoleHack GetNewTestConsole()
=> new ConsoleHack().RedirectToBuffer(true);

//private static (Pipeline pipeline, CliConfiguration configuration, ConsoleHack consoleHack) StandardObjects(VersionSubsystem versionSubsystem)
//{
// var configuration = new CliConfiguration(new CliRootCommand { new CliOption<bool>("-x") });
// var pipeline = new Pipeline
// {
// Version = versionSubsystem
// };
// var consoleHack = new ConsoleHack().RedirectToBuffer(true);
// return (pipeline, configuration, consoleHack);
//}

[Theory]
[InlineData("-v", true)]
[InlineData("--version", true)]
[InlineData("-x", false)]
[InlineData("", false)]
[InlineData(null, false)]
[ClassData(typeof(TestData.Version))]
public void Subsystem_runs_in_pipeline_only_when_requested(string input, bool shouldRun)
{
var configuration = new CliConfiguration(new CliRootCommand { });
var pipeline = new Pipeline
{
Version = new VersionSubsystem()
};
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
var pipeline = GetTestPipeline(new VersionSubsystem());
var console = GetNewTestConsole();

var exit = pipeline.Execute(configuration, input, consoleHack);
var exit = pipeline.Execute(GetNewTestConfiguration(), input, console);

exit.ExitCode.Should().Be(0);
exit.Handled.Should().Be(shouldRun);
if (shouldRun)
{
consoleHack.GetBuffer().Trim().Should().Be(version);
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
}
}

[Theory]
[InlineData("-v", true)]
[InlineData("--version", true)]
[InlineData("-x", false)]
[InlineData("", false)]
[InlineData(null, false)]
[ClassData(typeof(TestData.Version))]
public void Subsystem_runs_with_explicit_parse_only_when_requested(string input, bool shouldRun)
{
var configuration = new CliConfiguration(new CliRootCommand { });
var pipeline = new Pipeline
{
Version = new VersionSubsystem()
};
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
var pipeline = GetTestPipeline(new VersionSubsystem());
var console = GetNewTestConsole();

var result = pipeline.Parse(configuration, input);
var exit = pipeline.Execute(result, input, consoleHack);
var result = pipeline.Parse(GetNewTestConfiguration(), input);
var exit = pipeline.Execute(result, input, console);

exit.ExitCode.Should().Be(0);
exit.Handled.Should().Be(shouldRun);
if (shouldRun)
{
consoleHack.GetBuffer().Trim().Should().Be(version);
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
}
}

[Theory]
[InlineData("-v", true)]
[InlineData("--version", true)]
[InlineData("-x", false)]
[InlineData("", false)]
[InlineData(null, false)]
[ClassData(typeof(TestData.Version))]
public void Subsystem_runs_initialize_and_teardown_when_requested(string input, bool shouldRun)
{
var configuration = new CliConfiguration(new CliRootCommand { });
AlternateSubsystems.VersionWithInitializeAndTeardown versionSubsystem = new AlternateSubsystems.VersionWithInitializeAndTeardown();
var pipeline = new Pipeline
{
Version = versionSubsystem
};
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
var versionSubsystem = new AlternateSubsystems.VersionWithInitializeAndTeardown();
var pipeline = GetTestPipeline(versionSubsystem);
var console = GetNewTestConsole();

var exit = pipeline.Execute(configuration, input, consoleHack);
var exit = pipeline.Execute(GetNewTestConfiguration(), input, console);

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


[Theory]
[InlineData("-v", true)]
[InlineData("--version", true)]
[InlineData("-x", false)]
[InlineData("", false)]
[InlineData(null, false)]
public void Subsystem_can_be_used_without_runner(string input, bool shouldRun)
[ClassData(typeof(TestData.Version))]
public void Subsystem_works_without_pipeline(string input, bool shouldRun)
{
var configuration = new CliConfiguration(new CliRootCommand { });
var versionSubsystem = new VersionSubsystem();
var consoleHack = new ConsoleHack().RedirectToBuffer(true);

Subsystem.Initialize(versionSubsystem, configuration);
// 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
var parseResult = CliParser.Parse(configuration.RootCommand, input, configuration);
// TODO: Ensure an efficient conversion as people may copy this code
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
var console = GetNewTestConsole();
var configuration = GetNewTestConfiguration();

Subsystem.Initialize(versionSubsystem, configuration, args);
// This approach might be taken if someone is using a subsystem just for initialization
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
bool value = parseResult.GetValue<bool>("--version");

parseResult.Errors.Should().BeEmpty();
value.Should().Be(shouldRun);
if (shouldRun)
if (shouldRun)
{
// TODO: Add an execute overload to avoid checking activated twice
var exit = Subsystem.Execute(versionSubsystem, parseResult, input, consoleHack);
var exit = Subsystem.Execute(versionSubsystem, parseResult, input, console);
exit.Should().NotBeNull();
exit.ExitCode.Should().Be(0);
exit.Handled.Should().BeTrue();
consoleHack.GetBuffer().Trim().Should().Be(version);
console.GetBuffer().Trim().Should().Be(TestData.AssemblyVersionString);
}
}

[Theory]
[InlineData("-v", true)]
[InlineData("--version", true)]
[InlineData("-x", false)]
[InlineData("", false)]
[InlineData(null, false)]
public void Subsystem_can_be_used_without_runner_style2(string input, bool shouldRun)
[ClassData(typeof(TestData.Version))]
public void Subsystem_works_without_pipeline_style2(string input, bool shouldRun)
{
var configuration = new CliConfiguration(new CliRootCommand { });
var versionSubsystem = new VersionSubsystem();
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
var console = GetNewTestConsole();
var configuration = GetNewTestConfiguration();
var expectedVersion = shouldRun
? version
? TestData.AssemblyVersionString
: "";

Subsystem.Initialize(versionSubsystem, configuration);
var parseResult = CliParser.Parse(configuration.RootCommand, input, configuration);
var exit = Subsystem.ExecuteIfNeeded(versionSubsystem, parseResult, input, consoleHack);
// Someone might use this approach if they wanted to do something with the ParseResult
Subsystem.Initialize(versionSubsystem, configuration, args);
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
var exit = Subsystem.ExecuteIfNeeded(versionSubsystem, parseResult, input, console);

exit.ExitCode.Should().Be(0);
exit.Handled.Should().Be(shouldRun);
consoleHack.GetBuffer().Trim().Should().Be(expectedVersion);
console.GetBuffer().Trim().Should().Be(expectedVersion);
}


[Theory]
[InlineData("-xy", false)]
[InlineData("--versionx", false)]
public void Subsystem_runs_when_requested_even_when_there_are_errors(string input, bool shouldRun)
{
var versionSubsystem = new VersionSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
var configuration = GetNewTestConfiguration();

Subsystem.Initialize(versionSubsystem, configuration, args);
// This approach might be taken if someone is using a subsystem just for initialization
var parseResult = CliParser.Parse(configuration.RootCommand, args, configuration);
bool value = parseResult.GetValue<bool>("--version");

parseResult.Errors.Should().NotBeEmpty();
value.Should().Be(shouldRun);
}

[Fact]
Expand Down Expand Up @@ -171,9 +178,8 @@ public void Normal_pipeline_contains_no_subsystems()
public void Subsystems_can_access_each_others_data()
{
// TODO: Explore a mechanism that doesn't require the reference to retrieve data, this shows that it is awkward
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
var symbol = new CliOption<bool>("-x");

var console = GetNewTestConsole();
var pipeline = new StandardPipeline
{
Version = new AlternateSubsystems.VersionThatUsesHelpData(symbol)
Expand All @@ -183,9 +189,10 @@ public void Subsystems_can_access_each_others_data()
{
symbol.With(pipeline.Help.Description, "Testing")
};
pipeline.Execute(new CliConfiguration(rootCommand), "-v", consoleHack);
consoleHack.GetBuffer().Trim().Should().Be($"Testing");
}

pipeline.Execute(new CliConfiguration(rootCommand), "-v", console);

console.GetBuffer().Trim().Should().Be($"Testing");
}
}
}
Loading