diff --git a/docs/configuration.md b/docs/configuration.md index 7de0fb9468..e17625126f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -683,3 +683,12 @@ Command line: `--break-on-initial-test-failure` Config file: `break-on-initial-test-failure` Instruct Stryker to break execution when at least one test failed on initial test run. + +### `skip-version-check` <`flag`> + +Default: `false` +Command line: `--skip-version-check` +Config file: `N/A` + +This flag disables the automatic version check for Stryker updates when running the tool. +Use this option if you want to prevent Stryker from checking for newer versions, such as in environments with restricted internet access or where update checking is unnecessary. diff --git a/src/Stryker.CLI/Stryker.CLI.UnitTest/StrykerCLITests.cs b/src/Stryker.CLI/Stryker.CLI.UnitTest/StrykerCLITests.cs index d830921f65..58d5370eb5 100644 --- a/src/Stryker.CLI/Stryker.CLI.UnitTest/StrykerCLITests.cs +++ b/src/Stryker.CLI/Stryker.CLI.UnitTest/StrykerCLITests.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -41,7 +40,6 @@ public StrykerCLITests() .Returns(_runResults) .Verifiable(); _nugetClientMock.Setup(x => x.GetLatestVersionAsync()).Returns(Task.FromResult(new SemanticVersion(10, 0, 0))); - _nugetClientMock.Setup(x => x.GetPreviewVersionAsync()).Returns(Task.FromResult(new SemanticVersion(20, 0, 0))); _target = new StrykerCli(_strykerRunnerMock.Object, null, _loggingInitializerMock.Object, _nugetClientMock.Object); } @@ -86,6 +84,28 @@ public void ShouldDisplayLogo() consoleOutput.ShouldContain("Version:"); consoleOutput.ShouldContain(@"A new version of Stryker.NET (10.0.0) is available. Please consider upgrading using `dotnet tool update -g dotnet-stryker`"); + + _nugetClientMock.Verify(x => x.GetLatestVersionAsync(), Times.Once); + } + + [TestMethod] + public void ShouldCallNugetClient() + { + _target.Run([]); + + _nugetClientMock.Verify(x => x.GetLatestVersionAsync(), Times.Once); + _nugetClientMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void OnAlreadyNewestVersion_ShouldCallNugetClientForPreview() + { + _nugetClientMock.Setup(x => x.GetLatestVersionAsync()).Returns(Task.FromResult(new SemanticVersion(0, 0, 0))); + _nugetClientMock.Setup(x => x.GetPreviewVersionAsync()).Returns(Task.FromResult(new SemanticVersion(20, 0, 0))); + + _target.Run([]); + + _nugetClientMock.VerifyAll(); } [TestMethod] @@ -455,4 +475,15 @@ public void ShouldSupplyTargetFrameworkWhenPassed(params string[] argName) _inputs.TargetFrameworkInput.SuppliedInput.ShouldBe("net7.0"); } + + [TestMethod] + [DataRow("--skip-version-check")] + public void ShouldSupplyDisableCheckForNewerVersion(params string[] argName) + { + _target.Run(argName); + + _strykerRunnerMock.VerifyAll(); + + _nugetClientMock.VerifyNoOtherCalls(); + } } diff --git a/src/Stryker.CLI/Stryker.CLI/CommandLineConfig/CommandLineConfigReader.cs b/src/Stryker.CLI/Stryker.CLI/CommandLineConfig/CommandLineConfigReader.cs index 87465424e6..a30357b287 100644 --- a/src/Stryker.CLI/Stryker.CLI/CommandLineConfig/CommandLineConfigReader.cs +++ b/src/Stryker.CLI/Stryker.CLI/CommandLineConfig/CommandLineConfigReader.cs @@ -8,251 +8,263 @@ using Stryker.Core.Options; using Stryker.Core.Options.Inputs; -namespace Stryker.CLI.CommandLineConfig +namespace Stryker.CLI.CommandLineConfig; + +public class CommandLineConfigReader { - public class CommandLineConfigReader + private readonly IAnsiConsole _console; + private readonly IDictionary _cliInputs = new Dictionary(); + private readonly CliInput _configFileInput; + private readonly CliInput _skipVersionCheckInput; + + public CommandLineConfigReader(IAnsiConsole console = null) { - private readonly IDictionary _cliInputs = new Dictionary(); - private readonly CliInput _configFileInput; - private readonly IAnsiConsole _console; + _configFileInput = AddCliOnlyInput("config-file", "f", "Choose the file containing your stryker configuration relative to current working directory. Supports json and yaml formats. | default: stryker-config.json", argumentHint: "relative-path"); + _skipVersionCheckInput = AddCliOnlyInput("skip-version-check", null, "Skips check for newer version. | default: false", optionType: CommandOptionType.NoValue, category: InputCategory.Misc); - public CommandLineConfigReader(IAnsiConsole console = null) { - _configFileInput = AddCliOnlyInput("config-file", "f", "Choose the file containing your stryker configuration relative to current working directory. Supports json and yaml formats. | default: stryker-config.json", argumentHint: "relative-path"); - _console = console ?? AnsiConsole.Console; - } + _console = console ?? AnsiConsole.Console; + } - public void RegisterCommandLineOptions(CommandLineApplication app, IStrykerInputs inputs) - { - PrepareCliOptions(inputs); + public void RegisterCommandLineOptions(CommandLineApplication app, IStrykerInputs inputs) + { + PrepareCliOptions(inputs); - RegisterCliInputs(app); - } + RegisterCliInputs(app); + } - public void RegisterInitCommand(CommandLineApplication app, IFileSystem fileSystem, IStrykerInputs inputs, string[] args) + public void RegisterInitCommand(CommandLineApplication app, IFileSystem fileSystem, IStrykerInputs inputs, string[] args) => + app.Command("init", initCommandApp => { - app.Command("init", initCommandApp => - { - RegisterCliInputs(initCommandApp); + RegisterCliInputs(initCommandApp); - initCommandApp.OnExecute(() => - { - _console.WriteLine($"Initializing new config file."); - _console.WriteLine(); + initCommandApp.OnExecute(() => + { + _console.WriteLine($"Initializing new config file."); + _console.WriteLine(); - ReadCommandLineConfig(args[1..], initCommandApp, inputs); - var configOption = initCommandApp.Options.SingleOrDefault(o => o.LongName == _configFileInput.ArgumentName); - var basePath = fileSystem.Directory.GetCurrentDirectory(); - var configFilePath = Path.Combine(basePath, configOption?.Value() ?? "stryker-config.json"); + ReadCommandLineConfig(args[1..], initCommandApp, inputs); + var configOption = initCommandApp.Options.SingleOrDefault(o => o.LongName == _configFileInput.ArgumentName); + var basePath = fileSystem.Directory.GetCurrentDirectory(); + var configFilePath = Path.Combine(basePath, configOption?.Value() ?? "stryker-config.json"); - if (fileSystem.File.Exists(configFilePath)) + if (fileSystem.File.Exists(configFilePath)) + { + _console.Write("Config file already exists at "); + _console.WriteLine(configFilePath, new Style(Color.Cyan1)); + var overwrite = _console.Confirm($"Do you want to overwrite it?", false); + if (!overwrite) { - _console.Write("Config file already exists at "); - _console.WriteLine(configFilePath, new Style(Color.Cyan1)); - var overwrite = _console.Confirm($"Do you want to overwrite it?", false); - if (!overwrite) - { - return; - } - _console.WriteLine(); + return; } + _console.WriteLine(); + } - var config = FileConfigGenerator.GenerateConfigAsync(inputs); - fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(configFilePath)); - fileSystem.File.WriteAllText(configFilePath, config); + var config = FileConfigGenerator.GenerateConfigAsync(inputs); + _ = fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(configFilePath)); + fileSystem.File.WriteAllText(configFilePath, config); - _console.Write("Config file written to "); - _console.WriteLine(configFilePath, new Style(Color.Cyan1)); - _console.Write("The file is populated with default values, remove the options you don't need and edit the options you want to use. For more information on configuring stryker see: "); - _console.WriteLine("https://stryker-mutator.io/docs/stryker-net/configuration", new Style(Color.Cyan1)); - }); + _console.Write("Config file written to "); + _console.WriteLine(configFilePath, new Style(Color.Cyan1)); + _console.Write("The file is populated with default values, remove the options you don't need and edit the options you want to use. For more information on configuring stryker see: "); + _console.WriteLine("https://stryker-mutator.io/docs/stryker-net/configuration", new Style(Color.Cyan1)); }); - } + }); - public CommandOption GetConfigFileOption(string[] args, CommandLineApplication app) - { - var commands = app.Parse(args); - return commands.SelectedCommand.Options.SingleOrDefault(o => o.LongName == _configFileInput.ArgumentName); - } - - public void ReadCommandLineConfig(string[] args, CommandLineApplication app, IStrykerInputs inputs) - { - foreach (var cliInput in app.Parse(args).SelectedCommand.Options.Where(option => option.HasValue())) - { - var strykerInput = GetStrykerInput(cliInput); - - switch (cliInput.OptionType) - { - case CommandOptionType.NoValue: - HandleNoValue((IInput)strykerInput); - break; - - case CommandOptionType.MultipleValue: - HandleMultiValue(cliInput, (IInput>)strykerInput); - break; - - case CommandOptionType.SingleOrNoValue: - HandleSingleOrNoValue(strykerInput, cliInput, inputs); - break; - } - - switch (strykerInput) - { - case IInput stringInput: - HandleSingleStringValue(cliInput, stringInput); - break; - - case IInput nullableIntInput: - HandleSingleIntValue(cliInput, nullableIntInput); - break; + public CommandOption GetConfigFileOption(string[] args, CommandLineApplication app) + { + var commands = app.Parse(args); + return commands.SelectedCommand.Options.SingleOrDefault(o => o.LongName == _configFileInput.ArgumentName); + } - case IInput intInput: - HandleSingleIntValue(cliInput, (IInput)intInput); - break; - } - } - } + public CommandOption GetSkipVersionCheckOption(string[] args, CommandLineApplication app) + { + var commands = app.Parse(args); + return commands.SelectedCommand.Options.SingleOrDefault(o => o.LongName == _skipVersionCheckInput.ArgumentName); + } - private void RegisterCliInputs(CommandLineApplication app) + public void ReadCommandLineConfig(string[] args, CommandLineApplication app, IStrykerInputs inputs) + { + foreach (var cliInput in app.Parse(args).SelectedCommand.Options.Where(option => option.HasValue())) { - foreach (var (_, value) in _cliInputs) + var strykerInput = GetStrykerInput(cliInput); + + if (strykerInput is null) { - RegisterCliInput(app, value); + continue; // not a stryker input } - } - private static void HandleNoValue(IInput strykerInput) => strykerInput.SuppliedInput = true; + switch (cliInput.OptionType) + { + case CommandOptionType.NoValue: + HandleNoValue((IInput)strykerInput); + break; - private static void HandleSingleStringValue(CommandOption cliInput, IInput strykerInput) => strykerInput.SuppliedInput = cliInput.Value(); + case CommandOptionType.MultipleValue: + HandleMultiValue(cliInput, (IInput>)strykerInput); + break; - private static void HandleSingleIntValue(CommandOption cliInput, IInput strykerInput) - { - if (int.TryParse(cliInput.Value(), out var value)) - { - strykerInput.SuppliedInput = value; - } - else - { - throw new InputException($"Unexpected value for argument {cliInput.LongName}:{cliInput.Value()}. Expected type to be integer"); + case CommandOptionType.SingleOrNoValue: + HandleSingleOrNoValue(strykerInput, cliInput, inputs); + break; } - } - private static void HandleSingleOrNoValue(IInput strykerInput, CommandOption cliInput, IStrykerInputs inputs) - { switch (strykerInput) { - // handle single or no value inputs - case SinceInput sinceInput: - sinceInput.SuppliedInput = true; - inputs.SinceTargetInput.SuppliedInput = cliInput.Value(); + case IInput stringInput: + HandleSingleStringValue(cliInput, stringInput); break; - case WithBaselineInput withBaselineInput: - withBaselineInput.SuppliedInput = true; - inputs.SinceTargetInput.SuppliedInput = cliInput.Value(); + case IInput nullableIntInput: + HandleSingleIntValue(cliInput, nullableIntInput); break; - case OpenReportInput openReportInput: - openReportInput.SuppliedInput = cliInput.Value(); - inputs.OpenReportEnabledInput.SuppliedInput = true; + case IInput intInput: + HandleSingleIntValue(cliInput, (IInput)intInput); break; } } + } + + private void RegisterCliInputs(CommandLineApplication app) + { + foreach (var (_, value) in _cliInputs) + { + RegisterCliInput(app, value); + } + } - private static void HandleMultiValue(CommandOption cliInput, IInput> strykerInput) => strykerInput.SuppliedInput = cliInput.Values; + private static void HandleNoValue(IInput strykerInput) => strykerInput.SuppliedInput = true; - private IInput GetStrykerInput(CommandOption cliInput) => _cliInputs[cliInput.LongName].Input; + private static void HandleSingleStringValue(CommandOption cliInput, IInput strykerInput) => strykerInput.SuppliedInput = cliInput.Value(); - private void PrepareCliOptions(IStrykerInputs inputs) + private static void HandleSingleIntValue(CommandOption cliInput, IInput strykerInput) + { + if (int.TryParse(cliInput.Value(), out var value)) + { + strykerInput.SuppliedInput = value; + } + else { - // Category: Generic - AddCliInput(inputs.ThresholdBreakInput, "break-at", "b", argumentHint: "0-100"); - AddCliInput(inputs.ThresholdHighInput, "threshold-high", "", argumentHint: "0-100"); - AddCliInput(inputs.ThresholdLowInput, "threshold-low", "", argumentHint: "0-100"); - AddCliInput(inputs.LogToFileInput, "log-to-file", "L", optionType: CommandOptionType.NoValue); - AddCliInput(inputs.VerbosityInput, "verbosity", "V"); - AddCliInput(inputs.ConcurrencyInput, "concurrency", "c", argumentHint: "number"); - AddCliInput(inputs.DisableBailInput, "disable-bail", null, optionType: CommandOptionType.NoValue); - // Category: Build - AddCliInput(inputs.SolutionInput, "solution", "s", argumentHint: "file-path", category: InputCategory.Build); - AddCliInput(inputs.ConfigurationInput, "configuration", null, argumentHint: "Release,Debug", category: InputCategory.Build); - AddCliInput(inputs.SourceProjectNameInput, "project", "p", argumentHint: "project-name.csproj", category: InputCategory.Build); - AddCliInput(inputs.TestProjectsInput, "test-project", "tp", CommandOptionType.MultipleValue, InputCategory.Build); - AddCliInput(inputs.MsBuildPathInput, "msbuild-path", null, category: InputCategory.Build); - AddCliInput(inputs.TargetFrameworkInput, "target-framework", null, optionType: CommandOptionType.SingleValue, category: InputCategory.Build); - // Category: Mutation - AddCliInput(inputs.MutateInput, "mutate", "m", optionType: CommandOptionType.MultipleValue, argumentHint: "glob-pattern", category: InputCategory.Mutation); - AddCliInput(inputs.MutationLevelInput, "mutation-level", "l", category: InputCategory.Mutation); - AddCliInput(inputs.SinceInput, "since", "", optionType: CommandOptionType.SingleOrNoValue, argumentHint: "committish", category: InputCategory.Mutation); - AddCliInput(inputs.WithBaselineInput, "with-baseline", "", optionType: CommandOptionType.SingleOrNoValue, argumentHint: "committish", category: InputCategory.Mutation); - // Category: Reporting - AddCliInput(inputs.OpenReportInput, "open-report", "o", CommandOptionType.SingleOrNoValue, argumentHint: "report-type", category: InputCategory.Reporting); - AddCliInput(inputs.ReportersInput, "reporter", "r", optionType: CommandOptionType.MultipleValue, category: InputCategory.Reporting); - AddCliInput(inputs.ProjectVersionInput, "version", "v", category: InputCategory.Reporting); - AddCliInput(inputs.DashboardApiKeyInput, "dashboard-api-key", null, category: InputCategory.Reporting); - AddCliInput(inputs.AzureFileStorageSasInput, "azure-fileshare-sas", null, category: InputCategory.Reporting); - AddCliInput(inputs.OutputPathInput, "output", "O", optionType: CommandOptionType.SingleValue, category: InputCategory.Reporting); - // Category: Misc - AddCliInput(inputs.BreakOnInitialTestFailureInput, "break-on-initial-test-failure", null, optionType: CommandOptionType.NoValue, category: InputCategory.Misc); - AddCliInput(inputs.DevModeInput, "dev-mode", null, optionType: CommandOptionType.NoValue, category: InputCategory.Misc); + throw new InputException($"Unexpected value for argument {cliInput.LongName}:{cliInput.Value()}. Expected type to be integer"); } + } - private void RegisterCliInput(CommandLineApplication app, CliInput option) + private static void HandleSingleOrNoValue(IInput strykerInput, CommandOption cliInput, IStrykerInputs inputs) + { + switch (strykerInput) { - var argumentHint = option.OptionType switch - { - CommandOptionType.NoValue => "", - CommandOptionType.SingleOrNoValue => $"[:<{option.ArgumentHint}>]", - _ => $" <{option.ArgumentHint}>" - }; + // handle single or no value inputs + case SinceInput sinceInput: + sinceInput.SuppliedInput = true; + inputs.SinceTargetInput.SuppliedInput = cliInput.Value(); + break; + + case WithBaselineInput withBaselineInput: + withBaselineInput.SuppliedInput = true; + inputs.SinceTargetInput.SuppliedInput = cliInput.Value(); + break; + + case OpenReportInput openReportInput: + openReportInput.SuppliedInput = cliInput.Value(); + inputs.OpenReportEnabledInput.SuppliedInput = true; + break; + } + } - var commandOption = new StrykerInputOption($"-{option.ArgumentShortName}|--{option.ArgumentName}{argumentHint}", option.OptionType, option.Category) - { - Description = option.Description - }; + private static void HandleMultiValue(CommandOption cliInput, IInput> strykerInput) => strykerInput.SuppliedInput = cliInput.Values; - app.AddOption(commandOption); - } + private IInput GetStrykerInput(CommandOption cliInput) => _cliInputs[cliInput.LongName].Input; + + private void PrepareCliOptions(IStrykerInputs inputs) + { + // Category: Generic + AddCliInput(inputs.ThresholdBreakInput, "break-at", "b", argumentHint: "0-100"); + AddCliInput(inputs.ThresholdHighInput, "threshold-high", "", argumentHint: "0-100"); + AddCliInput(inputs.ThresholdLowInput, "threshold-low", "", argumentHint: "0-100"); + AddCliInput(inputs.LogToFileInput, "log-to-file", "L", optionType: CommandOptionType.NoValue); + AddCliInput(inputs.VerbosityInput, "verbosity", "V"); + AddCliInput(inputs.ConcurrencyInput, "concurrency", "c", argumentHint: "number"); + AddCliInput(inputs.DisableBailInput, "disable-bail", null, optionType: CommandOptionType.NoValue); + // Category: Build + AddCliInput(inputs.SolutionInput, "solution", "s", argumentHint: "file-path", category: InputCategory.Build); + AddCliInput(inputs.ConfigurationInput, "configuration", null, argumentHint: "Release,Debug", category: InputCategory.Build); + AddCliInput(inputs.SourceProjectNameInput, "project", "p", argumentHint: "project-name.csproj", category: InputCategory.Build); + AddCliInput(inputs.TestProjectsInput, "test-project", "tp", CommandOptionType.MultipleValue, InputCategory.Build); + AddCliInput(inputs.MsBuildPathInput, "msbuild-path", null, category: InputCategory.Build); + AddCliInput(inputs.TargetFrameworkInput, "target-framework", null, optionType: CommandOptionType.SingleValue, category: InputCategory.Build); + // Category: Mutation + AddCliInput(inputs.MutateInput, "mutate", "m", optionType: CommandOptionType.MultipleValue, argumentHint: "glob-pattern", category: InputCategory.Mutation); + AddCliInput(inputs.MutationLevelInput, "mutation-level", "l", category: InputCategory.Mutation); + AddCliInput(inputs.SinceInput, "since", "", optionType: CommandOptionType.SingleOrNoValue, argumentHint: "committish", category: InputCategory.Mutation); + AddCliInput(inputs.WithBaselineInput, "with-baseline", "", optionType: CommandOptionType.SingleOrNoValue, argumentHint: "committish", category: InputCategory.Mutation); + // Category: Reporting + AddCliInput(inputs.OpenReportInput, "open-report", "o", CommandOptionType.SingleOrNoValue, argumentHint: "report-type", category: InputCategory.Reporting); + AddCliInput(inputs.ReportersInput, "reporter", "r", optionType: CommandOptionType.MultipleValue, category: InputCategory.Reporting); + AddCliInput(inputs.ProjectVersionInput, "version", "v", category: InputCategory.Reporting); + AddCliInput(inputs.DashboardApiKeyInput, "dashboard-api-key", null, category: InputCategory.Reporting); + AddCliInput(inputs.AzureFileStorageSasInput, "azure-fileshare-sas", null, category: InputCategory.Reporting); + AddCliInput(inputs.OutputPathInput, "output", "O", optionType: CommandOptionType.SingleValue, category: InputCategory.Reporting); + // Category: Misc + AddCliInput(inputs.BreakOnInitialTestFailureInput, "break-on-initial-test-failure", null, optionType: CommandOptionType.NoValue, category: InputCategory.Misc); + AddCliInput(inputs.DevModeInput, "dev-mode", null, optionType: CommandOptionType.NoValue, category: InputCategory.Misc); + } - private CliInput AddCliOnlyInput(string argumentName, string argumentShortName, string helpText, - CommandOptionType optionType = CommandOptionType.SingleValue, InputCategory category = InputCategory.Generic, string argumentHint = null) + private void RegisterCliInput(CommandLineApplication app, CliInput option) + { + var argumentHint = option.OptionType switch { - var cliOption = new CliInput - { - ArgumentName = argumentName, - ArgumentShortName = argumentShortName, - Description = helpText, - OptionType = optionType, - ArgumentHint = argumentHint, - Category = category - }; + CommandOptionType.NoValue => "", + CommandOptionType.SingleOrNoValue => $"[:<{option.ArgumentHint}>]", + _ => $" <{option.ArgumentHint}>" + }; - _cliInputs[argumentName] = cliOption; + var commandOption = new StrykerInputOption($"-{option.ArgumentShortName}|--{option.ArgumentName}{argumentHint}", option.OptionType, option.Category) + { + Description = option.Description + }; - return cliOption; - } + app.AddOption(commandOption); + } - private void AddCliInput(IInput input, string argumentName, string argumentShortName, - CommandOptionType optionType = CommandOptionType.SingleValue, InputCategory category = InputCategory.Generic, string argumentHint = null) + private CliInput AddCliOnlyInput(string argumentName, string argumentShortName, string helpText, + CommandOptionType optionType = CommandOptionType.SingleValue, InputCategory category = InputCategory.Generic, string argumentHint = null) + { + var cliOption = new CliInput { - var cliOption = new CliInput - { - Input = input, - ArgumentName = argumentName, - ArgumentShortName = argumentShortName, - Description = input.HelpText, - OptionType = optionType, - Category = category, - ArgumentHint = argumentHint - }; - - _cliInputs[argumentName] = cliOption; - } + ArgumentName = argumentName, + ArgumentShortName = argumentShortName, + Description = helpText, + OptionType = optionType, + ArgumentHint = argumentHint, + Category = category + }; + + _cliInputs[argumentName] = cliOption; + + return cliOption; } - public class StrykerInputOption : CommandOption + private void AddCliInput(IInput input, string argumentName, string argumentShortName, + CommandOptionType optionType = CommandOptionType.SingleValue, InputCategory category = InputCategory.Generic, string argumentHint = null) { - public InputCategory Category { get; private set; } - - public StrykerInputOption(string template, CommandOptionType optionType, InputCategory category) : base(template, optionType) => Category = category; + var cliOption = new CliInput + { + Input = input, + ArgumentName = argumentName, + ArgumentShortName = argumentShortName, + Description = input.HelpText, + OptionType = optionType, + Category = category, + ArgumentHint = argumentHint + }; + + _cliInputs[argumentName] = cliOption; } } + +public class StrykerInputOption : CommandOption +{ + public InputCategory Category { get; private set; } + + public StrykerInputOption(string template, CommandOptionType optionType, InputCategory category) : base(template, optionType) => Category = category; +} diff --git a/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs b/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs index 9e09c1ab29..f63fafdd93 100644 --- a/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs +++ b/src/Stryker.CLI/Stryker.CLI/StrykerCLI.cs @@ -14,174 +14,174 @@ using Stryker.Core; using Stryker.Core.Options; -namespace Stryker.CLI -{ +namespace Stryker.CLI; - internal class ConsoleWrapper : IConsole - { - private IAnsiConsole _console; +internal class ConsoleWrapper : IConsole +{ - public ConsoleWrapper(IAnsiConsole console) - { - _console = console; - Out = new OutWriter(this); - Error = new OutWriter(this); - } + private readonly IAnsiConsole _console; - public void ResetColor() => throw new NotImplementedException(); + public ConsoleWrapper(IAnsiConsole console) + { + _console = console; + Out = new OutWriter(this); + Error = new OutWriter(this); + } - public TextWriter Out { get; init;} - public TextWriter Error { get; init;} - public TextReader In { get; } - public bool IsInputRedirected { get; } - public bool IsOutputRedirected { get; } - public bool IsErrorRedirected { get; } - public ConsoleColor ForegroundColor { get; set; } - public ConsoleColor BackgroundColor { get; set; } + public void ResetColor() => throw new NotImplementedException(); + public TextWriter Out { get; init; } + public TextWriter Error { get; init; } + public TextReader In { get; } + public bool IsInputRedirected { get; } + public bool IsOutputRedirected { get; } + public bool IsErrorRedirected { get; } + public ConsoleColor ForegroundColor { get; set; } + public ConsoleColor BackgroundColor { get; set; } - public event ConsoleCancelEventHandler CancelKeyPress; - private class OutWriter : TextWriter - { - private ConsoleWrapper _host; + public event ConsoleCancelEventHandler CancelKeyPress; - public OutWriter(ConsoleWrapper host) => _host = host; + private class OutWriter : TextWriter + { + private readonly ConsoleWrapper _host; - public override Encoding Encoding => _host._console.Profile.Encoding; + public OutWriter(ConsoleWrapper host) => _host = host; - public override void Write(char value) + public override Encoding Encoding => _host._console.Profile.Encoding; + + public override void Write(char value) + { + if (value == '\r') { - if (value == '\r') - { - // Spectre adds a '\r' automatically - return; - } - _host._console.Write(value.ToString()); + // Spectre adds a '\r' automatically + return; } + _host._console.Write(value.ToString()); } } +} - public class StrykerCli +public class StrykerCli +{ + private readonly IStrykerRunner _stryker; + private readonly IConfigBuilder _configReader; + private readonly ILoggingInitializer _loggingInitializer; + private readonly IStrykerNugetFeedClient _nugetClient; + private readonly IAnsiConsole _console; + private readonly IFileSystem _fileSystem; + + public int ExitCode { get; private set; } = ExitCodes.Success; + + public StrykerCli(IStrykerRunner stryker = null, + IConfigBuilder configReader = null, + ILoggingInitializer loggingInitializer = null, + IStrykerNugetFeedClient nugetClient = null, + IAnsiConsole console = null, + IFileSystem fileSystem = null) { - private readonly IStrykerRunner _stryker; - private readonly IConfigBuilder _configReader; - private readonly ILoggingInitializer _loggingInitializer; - private readonly IStrykerNugetFeedClient _nugetClient; - private readonly IAnsiConsole _console; - private readonly IFileSystem _fileSystem; - - public int ExitCode { get; private set; } = ExitCodes.Success; - - public StrykerCli(IStrykerRunner stryker = null, - IConfigBuilder configReader = null, - ILoggingInitializer loggingInitializer = null, - IStrykerNugetFeedClient nugetClient = null, - IAnsiConsole console = null, - IFileSystem fileSystem = null) - { - _stryker = stryker ?? new StrykerRunner(); - _configReader = configReader ?? new ConfigBuilder(); - _loggingInitializer = loggingInitializer ?? new LoggingInitializer(); - _nugetClient = nugetClient ?? new StrykerNugetFeedClient(); - _console = console ?? AnsiConsole.Console; - _fileSystem = fileSystem ?? new FileSystem(); - } + _stryker = stryker ?? new StrykerRunner(); + _configReader = configReader ?? new ConfigBuilder(); + _loggingInitializer = loggingInitializer ?? new LoggingInitializer(); + _nugetClient = nugetClient ?? new StrykerNugetFeedClient(); + _console = console ?? AnsiConsole.Console; + _fileSystem = fileSystem ?? new FileSystem(); + } - /// - /// Analyzes the arguments and displays an interface to the user. Kicks off the program. - /// - /// User input - public int Run(string[] args) + /// + /// Analyzes the arguments and displays an interface to the user. Kicks off the program. + /// + /// User input + public int Run(string[] args) + { + var app = new CommandLineApplication(new ConsoleWrapper(_console)) { - var app = new CommandLineApplication(new ConsoleWrapper(_console)) - { - Name = "Stryker", - FullName = "Stryker: Stryker mutator for .Net", - Description = "Stryker mutator for .Net", - ExtendedHelpText = "Welcome to Stryker for .Net! Run dotnet stryker to kick off a mutation test run", - HelpTextGenerator = new GroupedHelpTextGenerator() - }; - app.HelpOption(); + Name = "Stryker", + FullName = "Stryker: Stryker mutator for .Net", + Description = "Stryker mutator for .Net", + ExtendedHelpText = "Welcome to Stryker for .Net! Run dotnet stryker to kick off a mutation test run", + HelpTextGenerator = new GroupedHelpTextGenerator() + }; + _ = app.HelpOption(); - var inputs = new StrykerInputs(); - var cmdConfigReader = new CommandLineConfigReader(_console); + var inputs = new StrykerInputs(); + var cmdConfigReader = new CommandLineConfigReader(_console); - cmdConfigReader.RegisterCommandLineOptions(app, inputs); - cmdConfigReader.RegisterInitCommand(app, _fileSystem, inputs, args); + cmdConfigReader.RegisterCommandLineOptions(app, inputs); + cmdConfigReader.RegisterInitCommand(app, _fileSystem, inputs, args); - app.OnExecute(() => - { - // app started - PrintStrykerASCIIName(); + app.OnExecute(() => + { + // app started + PrintStrykerASCIIName(); - _configReader.Build(inputs, args, app, cmdConfigReader); - _loggingInitializer.SetupLogOptions(inputs); + _configReader.Build(inputs, args, app, cmdConfigReader); + _loggingInitializer.SetupLogOptions(inputs); - PrintStrykerVersionInformationAsync(); - RunStryker(inputs); - return ExitCode; - }); + PrintStrykerVersionInformationAsync(cmdConfigReader.GetSkipVersionCheckOption(args, app).HasValue()); + RunStryker(inputs); + return ExitCode; + }); - try - { - return app.Execute(args); - } - catch (CommandParsingException ex) - { - Console.Error.WriteLine(ex.Message); + try + { + return app.Execute(args); + } + catch (CommandParsingException ex) + { + Console.Error.WriteLine(ex.Message); - if (ex is UnrecognizedCommandParsingException uex && uex.NearestMatches.Any()) + if (ex is UnrecognizedCommandParsingException uex && uex.NearestMatches.Any()) + { + Console.Error.WriteLine(); + Console.Error.WriteLine("Did you mean this?"); + foreach (var match in uex.NearestMatches) { - Console.Error.WriteLine(); - Console.Error.WriteLine("Did you mean this?"); - foreach(var match in uex.NearestMatches) - { - Console.Error.WriteLine(" " + match); - } + Console.Error.WriteLine(" " + match); } - - return ExitCodes.OtherError; } + + return ExitCodes.OtherError; } + } - private void RunStryker(IStrykerInputs inputs) - { - var result = _stryker.RunMutationTest(inputs, ApplicationLogging.LoggerFactory); + private void RunStryker(IStrykerInputs inputs) + { + var result = _stryker.RunMutationTest(inputs, ApplicationLogging.LoggerFactory); - HandleStrykerRunResult(inputs, result); - } + HandleStrykerRunResult(inputs, result); + } - private void HandleStrykerRunResult(IStrykerInputs inputs, StrykerRunResult result) - { - var logger = ApplicationLogging.LoggerFactory.CreateLogger(); + private void HandleStrykerRunResult(IStrykerInputs inputs, StrykerRunResult result) + { + var logger = ApplicationLogging.LoggerFactory.CreateLogger(); - if (double.IsNaN(result.MutationScore)) - { - logger.LogInformation("Stryker was unable to calculate a mutation score"); - } - else - { - logger.LogInformation("The final mutation score is {MutationScore:P2}", result.MutationScore); - } + if (double.IsNaN(result.MutationScore)) + { + logger.LogInformation("Stryker was unable to calculate a mutation score"); + } + else + { + logger.LogInformation("The final mutation score is {MutationScore:P2}", result.MutationScore); + } - if (result.ScoreIsLowerThanThresholdBreak()) - { - var thresholdBreak = (double)inputs.ValidateAll().Thresholds.Break / 100; - logger.LogWarning("Final mutation score is below threshold break. Crashing..."); + if (result.ScoreIsLowerThanThresholdBreak()) + { + var thresholdBreak = (double)inputs.ValidateAll().Thresholds.Break / 100; + logger.LogWarning("Final mutation score is below threshold break. Crashing..."); - _console.WriteLine(); - _console.MarkupLine($"[Red]The mutation score is lower than the configured break threshold of {thresholdBreak:P0}.[/]"); - _console.MarkupLine(" [Red]Looks like you've got some work to do :smiling_face_with_halo:[/]"); + _console.WriteLine(); + _console.MarkupLine($"[Red]The mutation score is lower than the configured break threshold of {thresholdBreak:P0}.[/]"); + _console.MarkupLine(" [Red]Looks like you've got some work to do :smiling_face_with_halo:[/]"); - ExitCode = ExitCodes.BreakThresholdViolated; - } + ExitCode = ExitCodes.BreakThresholdViolated; } + } - private void PrintStrykerASCIIName() - { - _console.MarkupLine(@"[Yellow] + private void PrintStrykerASCIIName() + { + _console.MarkupLine(@"[Yellow] _____ _ _ _ _ ______ _______   / ____| | | | | \ | | ____|__ __|  | (___ | |_ _ __ _ _| | _____ _ __ | \| | |__ | |   @@ -191,49 +191,54 @@ _____ _ _ _ _ ______ _______   __/ |   |___/   [/]"); - _console.WriteLine(); - } + _console.WriteLine(); + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Bug", "S3168:\"async\" methods should not return \"void\"", Justification = "This method is fire and forget. Task.Run also doesn't work in unit tests")] - private async void PrintStrykerVersionInformationAsync() - { - var logger = ApplicationLogging.LoggerFactory.CreateLogger(); - var assembly = Assembly.GetExecutingAssembly(); - var version = assembly.GetCustomAttribute()?.InformationalVersion; + [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Suppression is for sonarcloud which Roslyn does not know about.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Bug", "S3168:\"async\" methods should not return \"void\"", Justification = "This method is fire and forget. Task.Run also doesn't work in unit tests")] + private async void PrintStrykerVersionInformationAsync(bool skipVersionCheck) + { + var logger = ApplicationLogging.LoggerFactory.CreateLogger(); + var assembly = Assembly.GetExecutingAssembly(); + var version = assembly.GetCustomAttribute()?.InformationalVersion; - if (!SemanticVersion.TryParse(version, out var currentVersion)) + if (!SemanticVersion.TryParse(version, out var currentVersion)) + { + if (string.IsNullOrEmpty(version)) { - if (string.IsNullOrEmpty(version)) - { - logger.LogWarning("{Attribute} is missing in {Assembly} at {AssemblyLocation}", nameof(AssemblyInformationalVersionAttribute), assembly, assembly.Location); - } - else - { - logger.LogWarning("Failed to parse version {Version} as a semantic version", version); - } - return; + logger.LogWarning("{Attribute} is missing in {Assembly} at {AssemblyLocation}", nameof(AssemblyInformationalVersionAttribute), assembly, assembly.Location); } - - _console.MarkupLine($"Version: [Green]{currentVersion}[/]"); - logger.LogDebug("Stryker starting, version: {Version}", currentVersion); - _console.WriteLine(); - - var latestVersion = await _nugetClient.GetLatestVersionAsync(); - if (latestVersion > currentVersion) + else { - _console.MarkupLine($@"[Yellow]A new version of Stryker.NET ({latestVersion}) is available. Please consider upgrading using `dotnet tool update -g dotnet-stryker`[/]"); - _console.WriteLine(); + logger.LogWarning("Failed to parse version {Version} as a semantic version", version); } - else + return; + } + + _console.MarkupLine($"Version: [Green]{currentVersion}[/]"); + logger.LogDebug("Stryker starting, version: {Version}", currentVersion); + _console.WriteLine(); + + if (skipVersionCheck) + { + return; + } + + var latestVersion = await _nugetClient.GetLatestVersionAsync(); + if (latestVersion > currentVersion) + { + _console.MarkupLine($@"[Yellow]A new version of Stryker.NET ({latestVersion}) is available. Please consider upgrading using `dotnet tool update -g dotnet-stryker`[/]"); + _console.WriteLine(); + } + else + { + var previewVersion = await _nugetClient.GetPreviewVersionAsync(); + if (previewVersion > currentVersion) { - var previewVersion = await _nugetClient.GetPreviewVersionAsync(); - if (previewVersion > currentVersion) - { - _console.MarkupLine($@"[Cyan]A preview version of Stryker.NET ({previewVersion}) is available. + _console.MarkupLine($@"[Cyan]A preview version of Stryker.NET ({previewVersion}) is available. If you would like to try out this preview version you can install it with `dotnet tool update -g dotnet-stryker --version {previewVersion}` Since this is a preview feature things might not work as expected! Please report any findings on GitHub![/]"); - _console.WriteLine(); - } + _console.WriteLine(); } } }