Skip to content
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
Binary file added CommandLineParser.FluentValidations.dll
Binary file not shown.
54 changes: 54 additions & 0 deletions CommandLineParser.Tests/Command/CommandDiscoveryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using MatthiWare.CommandLine.Core.Command;
using Xunit;
using MatthiWare.CommandLine.Abstractions.Command;
using System.Reflection;

namespace MatthiWare.CommandLine.Tests.Command
{
public class CommandDiscoveryTests
{
[Fact]
public void DiscoverCommandFromAssemblyContainsCorrectTypes()
{
var cmdDiscovery = new CommandDiscoverer();

var resultTypes = cmdDiscovery.DiscoverCommandTypes(typeof(CommandDiscoveryTests), new[] { Assembly.GetExecutingAssembly() });

var invalidAbstractCommand = typeof(AbstractCommand);
var wrongGenericTypeCommand = typeof(WrongGenericTypeCommand);
var validCommand = typeof(ValidCommand);
var validCommand2 = typeof(ValidCommand2);

Assert.DoesNotContain(invalidAbstractCommand, resultTypes);
Assert.DoesNotContain(wrongGenericTypeCommand, resultTypes);
Assert.Contains(validCommand, resultTypes);
Assert.Contains(validCommand2, resultTypes);
}

[Fact]
public void DiscoveredCommandsAreRegisteredCorrectly()
{
var parser = new CommandLineParser<CommandDiscoveryTests>();

parser.DiscoverCommands(Assembly.GetExecutingAssembly());

Assert.Equal(2, parser.Commands.Count);
}

public abstract class AbstractCommand : Command<CommandDiscoveryTests>
{
}

public abstract class WrongGenericTypeCommand : Command<object>
{
}

public class ValidCommand : Command<CommandDiscoveryTests>
{
}

public class ValidCommand2 : Command<CommandDiscoveryTests, object>
{
}
}
}
4 changes: 2 additions & 2 deletions CommandLineParser.Tests/CommandLineParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public void AutoExecuteCommandsWithExceptionDoesntCrashTheParser()

Assert.True(result.HasErrors);

Assert.Equal(ex, result.Errors.First());
Assert.Equal(ex, result.Errors.First().GetBaseException());
}

[Fact]
Expand All @@ -202,7 +202,7 @@ public async Task AutoExecuteCommandsWithExceptionDoesntCrashTheParserAsync()

Assert.True(result.HasErrors);

Assert.Equal(ex, result.Errors.First());
Assert.Equal(ex, result.Errors.First().GetBaseException());
}

[Fact]
Expand Down
14 changes: 6 additions & 8 deletions CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ public void TestHelpDisplayFiresCorrectly(string[] args, bool fires)
var usagePrinterMock = new Mock<IUsagePrinter>();

usagePrinterMock.Setup(mock => mock.PrintUsage()).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<IArgument>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintCommandUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintOptionUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);

var parser = new CommandLineParser<Options>
{
Expand All @@ -41,7 +40,7 @@ public void TestHelpDisplayFiresCorrectly(string[] args, bool fires)

parser.Parse(args);

Assert.Equal<bool>(fires, calledFlag);
Assert.Equal(fires, calledFlag);
}

[Theory]
Expand All @@ -60,9 +59,8 @@ public async Task TestHelpDisplayFiresCorrectlyAsync(string[] args, bool fires)
var usagePrinterMock = new Mock<IUsagePrinter>();

usagePrinterMock.Setup(mock => mock.PrintUsage()).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<IArgument>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintCommandUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
usagePrinterMock.Setup(mock => mock.PrintOptionUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);

var parser = new CommandLineParser<Options>
{
Expand All @@ -75,7 +73,7 @@ public async Task TestHelpDisplayFiresCorrectlyAsync(string[] args, bool fires)

await parser.ParseAsync(args);

Assert.Equal<bool>(fires, calledFlag);
Assert.Equal(fires, calledFlag);
}

public class Options
Expand Down
76 changes: 76 additions & 0 deletions CommandLineParser.Tests/Usage/NoColorOutputTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using MatthiWare.CommandLine.Abstractions;
using MatthiWare.CommandLine.Abstractions.Usage;
using MatthiWare.CommandLine.Core.Attributes;
using MatthiWare.CommandLine.Core.Usage;
using Moq;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace MatthiWare.CommandLine.Tests.Usage
{
[Collection("Non-Parallel Collection")]
public class NoColorOutputTests
{
private readonly CommandLineParser<Options> parser;
private readonly IEnvironmentVariablesService variablesService;
private Action<ConsoleColor> consoleColorGetter;
private bool variableServiceResult;
private readonly UsagePrinter printer;

public NoColorOutputTests()
{
var envMock = new Mock<IEnvironmentVariablesService>();
envMock.SetupGet(env => env.NoColorRequested).Returns(() => variableServiceResult);

variablesService = envMock.Object;

var usageBuilderMock = new Mock<IUsageBuilder>();
usageBuilderMock.Setup(m => m.AddErrors(It.IsAny<IReadOnlyCollection<Exception>>())).Callback(() =>
{
consoleColorGetter(printer.m_currentConsoleColor);
});

parser = new CommandLineParser<Options>();

printer = new UsagePrinter(parser, usageBuilderMock.Object, variablesService);

parser.Printer = printer;
}

[Fact]
public void CheckUsageOutputRespectsNoColor()
{
ParseAndCheckNoColor(false);
ParseAndCheckNoColor(true);
}

private void ParseAndCheckNoColor(bool noColorOuput)
{
consoleColorGetter = noColorOuput ? (Action<ConsoleColor>)AssertNoColor : AssertColor;

variableServiceResult = noColorOuput;

parser.Parse(new string[] { "alpha" });
}

private void AssertNoColor(ConsoleColor color)
{
Assert.True(variablesService.NoColorRequested);
Assert.NotEqual(ConsoleColor.Red, color);
}

private void AssertColor(ConsoleColor color)
{
Assert.False(variablesService.NoColorRequested);
Assert.Equal(ConsoleColor.Red, color);
}

private class Options
{
[Required, Name("b")]
public bool MyBool { get; set; }
}
}
}
25 changes: 15 additions & 10 deletions CommandLineParser.Tests/Usage/UsagePrinterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void UsagePrinterPrintsOptionCorrectly()

parser.Parse(new[] { "-o", "--help" });

printerMock.Verify(mock => mock.PrintUsage(It.IsAny<IArgument>()), Times.Once());
printerMock.Verify(mock => mock.PrintOptionUsage(It.IsAny<ICommandLineOption>()), Times.Once());
}

[Fact]
Expand All @@ -105,7 +105,7 @@ public void UsagePrinterPrintsCommandCorrectly()

parser.Parse(new[] { "-o", "bla", "cmd", "--help" });

printerMock.Verify(mock => mock.PrintUsage(It.IsAny<IArgument>()), Times.Once());
printerMock.Verify(mock => mock.PrintCommandUsage(It.IsAny<ICommandLineCommand>()), Times.Once());
}

[Theory]
Expand All @@ -123,41 +123,46 @@ public void CustomInvokedPrinterWorksCorrectly(string[] args, bool cmdPassed, bo

var parser = new CommandLineParser<UsagePrinterGetsCalledOptions>(parserOptions);

parser.Printer = new UsagePrinter(parserOptions, parser, builderMock.Object);
parser.Printer = new UsagePrinter(parser, builderMock.Object);

parser.AddCommand<UsagePrinterCommandOptions>()
.Name("cmd")
.Required();

var result = parser.Parse(args);

builderMock.Verify(mock => mock.Print(), Times.Never());
builderMock.Verify(mock => mock.PrintCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommandContainer>()), Times.Never());
builderMock.Verify(mock => mock.PrintOption(It.IsAny<ICommandLineOption>()), Times.Never());
builderMock.Verify(mock => mock.Build(), Times.Never());
builderMock.Verify(mock => mock.AddCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommandContainer>()), Times.Never());
builderMock.Verify(mock => mock.AddOption(It.IsAny<ICommandLineOption>()), Times.Never());

if (result.HelpRequested)
{
parser.Printer.PrintUsage(result.HelpRequestedFor);
}

if (result.HasErrors)
{
foreach (var err in result.Errors)
{
if (!(err is BaseParserException baseParserException)) continue;
if (!(err is BaseParserException baseParserException))
{
continue;
}

parser.Printer.PrintUsage(baseParserException.Argument);
}
}

builderMock.Verify(
mock => mock.Print(),
mock => mock.Build(),
ToTimes(result.HelpRequested || result.HasErrors));

builderMock.Verify(
mock => mock.PrintCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommandContainer>()),
mock => mock.AddCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommand>()),
ToTimes(cmdPassed));

builderMock.Verify(
mock => mock.PrintOption(It.IsAny<ICommandLineOption>()),
mock => mock.AddOption(It.IsAny<ICommandLineOption>()),
ToTimes(optPassed));
}

Expand Down
8 changes: 8 additions & 0 deletions CommandLineParser.Tests/XUnitExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq.Expressions;
using MatthiWare.CommandLine.Abstractions.Parsing;
using Xunit;

namespace MatthiWare.CommandLine.Tests
{
Expand Down Expand Up @@ -31,4 +32,11 @@ public static bool AssertNoErrors<T>(this IParserResult<T> result, bool shouldTh
return false;
}
}

#pragma warning disable SA1402 // FileMayOnlyContainASingleType
[CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
public class NonParallelCollection
{
}
#pragma warning restore SA1402 // FileMayOnlyContainASingleType
}
11 changes: 11 additions & 0 deletions CommandLineParser/Abstractions/Command/ICommandDiscoverer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace MatthiWare.CommandLine.Abstractions.Command
{
public interface ICommandDiscoverer
{
IReadOnlyList<Type> DiscoverCommandTypes(Type optionType, Assembly[] assemblies);
}
}
13 changes: 13 additions & 0 deletions CommandLineParser/Abstractions/ICommandLineParser'.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -57,6 +58,18 @@ public interface ICommandLineParser<TOption>

#region Configuration

/// <summary>
/// Discovers commands and registers them from any given assembly
/// </summary>
/// <param name="assembly">Assembly containing the command types</param>
void DiscoverCommands(Assembly assembly);

/// <summary>
/// Discovers commands and registers them from any given assembly
/// </summary>
/// <param name="assemblies">Assemblies containing the command types</param>
void DiscoverCommands(Assembly[] assemblies);

/// <summary>
/// Configures a new or existing option
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace MatthiWare.CommandLine.Abstractions.Usage
{
/// <summary>
/// Environment variables
/// </summary>
public interface IEnvironmentVariablesService
{
/// <summary>
/// Inidicates if NO_COLOR environment variable has been set
/// https://no-color.org/
/// </summary>
bool NoColorRequested { get; }
}
}
Loading