From 6251bd7066612d4a1bf7a28845acb7e3dbee9b0c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 3 Jan 2025 11:56:19 -0800 Subject: [PATCH] Eliminate duplication of console code --- .../nunit4-console/Options/Options.cs | 6 + src/NUnitConsole/nunit4-console/Program.cs | 2 - .../nunit4-netcore-console/ColorConsole.cs | 150 -- .../ColorConsoleWriter.cs | 107 - .../nunit4-netcore-console/ColorStyle.cs | 59 - .../nunit4-netcore-console/ConsoleOptions.cs | 453 ---- .../nunit4-netcore-console/ConsoleRunner.cs | 529 ---- .../ConsoleTestResult.cs | 150 -- .../ExtendedTextWrapper.cs | 127 - .../ExtendedTextWriter.cs | 57 - .../nunit4-netcore-console/FileSystem.cs | 32 - .../FrameworkPackageSettings.cs | 95 - .../Options/DefaultOptionsProvider.cs | 19 - .../Options/IDefaultOptionsProvider.cs | 9 - .../Options/OptionParser.cs | 127 - .../nunit4-netcore-console/Options/Options.cs | 2154 ----------------- .../Options/OutputSpecification.cs | 91 - .../Options/TestNameParser.cs | 87 - .../nunit4-netcore-console/Program.cs | 24 - .../nunit4-netcore-console/ResultReporter.cs | 259 -- .../nunit4-netcore-console/ResultSummary.cs | 225 -- .../SafeAttributeAccess.cs | 69 - .../TestEventHandler.cs | 215 -- .../Utilities/SaveConsoleOutput.cs | 26 - .../nunit4-netcore-console.csproj | 21 + 25 files changed, 27 insertions(+), 5066 deletions(-) delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ColorConsole.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ColorConsoleWriter.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ColorStyle.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ConsoleOptions.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ConsoleRunner.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ConsoleTestResult.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ExtendedTextWrapper.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ExtendedTextWriter.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/FileSystem.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/FrameworkPackageSettings.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/DefaultOptionsProvider.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/IDefaultOptionsProvider.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/OptionParser.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/Options.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/OutputSpecification.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Options/TestNameParser.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ResultReporter.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/ResultSummary.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/SafeAttributeAccess.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/TestEventHandler.cs delete mode 100644 src/NUnitConsole/nunit4-netcore-console/Utilities/SaveConsoleOutput.cs diff --git a/src/NUnitConsole/nunit4-console/Options/Options.cs b/src/NUnitConsole/nunit4-console/Options/Options.cs index 0e8fe974b..eef823456 100644 --- a/src/NUnitConsole/nunit4-console/Options/Options.cs +++ b/src/NUnitConsole/nunit4-console/Options/Options.cs @@ -777,6 +777,9 @@ public OptionException(string message, string? optionName, Exception innerExcept } #if !PCL +#if NET8_0_OR_GREATER + [Obsolete] +#endif protected OptionException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -790,6 +793,9 @@ public string? OptionName } #if !PCL +#if NET8_0_OR_GREATER + [Obsolete] +#endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); diff --git a/src/NUnitConsole/nunit4-console/Program.cs b/src/NUnitConsole/nunit4-console/Program.cs index 5b0ed50d4..e1595b3cc 100644 --- a/src/NUnitConsole/nunit4-console/Program.cs +++ b/src/NUnitConsole/nunit4-console/Program.cs @@ -97,7 +97,6 @@ public static int Main(string[] args) return ConsoleRunner.INVALID_ARG; } -#if NETFRAMEWORK if (Options.RuntimeFrameworkSpecified) { var availableRuntimeService = engine.Services.GetService(); @@ -118,7 +117,6 @@ public static int Main(string[] args) if (!runtimeAvailable) WriteErrorMessage("Unavailable runtime framework requested: " + Options.RuntimeFramework); } -#endif if (Options.WorkDirectory != null) engine.WorkDirectory = Options.WorkDirectory; diff --git a/src/NUnitConsole/nunit4-netcore-console/ColorConsole.cs b/src/NUnitConsole/nunit4-netcore-console/ColorConsole.cs deleted file mode 100644 index 84ca437b3..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ColorConsole.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.ConsoleRunner -{ - /// - /// Sets the console color in the constructor and resets it in the dispose - /// - public class ColorConsole : IDisposable - { - private readonly ConsoleColor _originalColor; - - /// - /// Initializes a new instance of the class. - /// - /// The color style to use. - public ColorConsole(ColorStyle style) - { - _originalColor = Console.ForegroundColor; - Console.ForegroundColor = GetColor(style); - } - - /// - /// By using styles, we can keep everything consistent - /// - /// - /// - public static ConsoleColor GetColor(ColorStyle style) - { - ConsoleColor color = GetColorForStyle(style); - ConsoleColor bg = Console.BackgroundColor; - - if (color == bg || color == ConsoleColor.Red && bg == ConsoleColor.Magenta) - return bg == ConsoleColor.Black - ? ConsoleColor.White - : ConsoleColor.Black; - - return color; - } - - private static ConsoleColor GetColorForStyle(ColorStyle style) - { - switch (Console.BackgroundColor) - { - case ConsoleColor.White: - switch (style) - { - case ColorStyle.Header: - return ConsoleColor.Black; - case ColorStyle.SubHeader: - return ConsoleColor.Black; - case ColorStyle.SectionHeader: - return ConsoleColor.Blue; - case ColorStyle.Label: - return ConsoleColor.Black; - case ColorStyle.Value: - return ConsoleColor.Blue; - case ColorStyle.Pass: - return ConsoleColor.Green; - case ColorStyle.Failure: - return ConsoleColor.Red; - case ColorStyle.Warning: - return ConsoleColor.Black; - case ColorStyle.Error: - return ConsoleColor.Red; - case ColorStyle.Output: - return ConsoleColor.Black; - case ColorStyle.Help: - return ConsoleColor.Black; - case ColorStyle.Default: - default: - return ConsoleColor.Black; - } - - case ConsoleColor.Cyan: - case ConsoleColor.Green: - case ConsoleColor.Red: - case ConsoleColor.Magenta: - case ConsoleColor.Yellow: - switch (style) - { - case ColorStyle.Header: - return ConsoleColor.Black; - case ColorStyle.SubHeader: - return ConsoleColor.Black; - case ColorStyle.SectionHeader: - return ConsoleColor.Blue; - case ColorStyle.Label: - return ConsoleColor.Black; - case ColorStyle.Value: - return ConsoleColor.Black; - case ColorStyle.Pass: - return ConsoleColor.Black; - case ColorStyle.Failure: - return ConsoleColor.Red; - case ColorStyle.Warning: - return ConsoleColor.Yellow; - case ColorStyle.Error: - return ConsoleColor.Red; - case ColorStyle.Output: - return ConsoleColor.Black; - case ColorStyle.Help: - return ConsoleColor.Black; - case ColorStyle.Default: - default: - return ConsoleColor.Black; - } - - default: - switch (style) - { - case ColorStyle.Header: - return ConsoleColor.White; - case ColorStyle.SubHeader: - return ConsoleColor.Gray; - case ColorStyle.SectionHeader: - return ConsoleColor.Cyan; - case ColorStyle.Label: - return ConsoleColor.Green; - case ColorStyle.Value: - return ConsoleColor.White; - case ColorStyle.Pass: - return ConsoleColor.Green; - case ColorStyle.Failure: - return ConsoleColor.Red; - case ColorStyle.Warning: - return ConsoleColor.Yellow; - case ColorStyle.Error: - return ConsoleColor.Red; - case ColorStyle.Output: - return ConsoleColor.Gray; - case ColorStyle.Help: - return ConsoleColor.Green; - case ColorStyle.Default: - default: - return ConsoleColor.Green; - } - } - } - - /// - /// If color is enabled, restores the console colors to their defaults - /// - public void Dispose() - { - Console.ForegroundColor = _originalColor; - } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit4-netcore-console/ColorConsoleWriter.cs b/src/NUnitConsole/nunit4-netcore-console/ColorConsoleWriter.cs deleted file mode 100644 index 69d77653e..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ColorConsoleWriter.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.IO; -using System.Text; -using NUnit.Common; - -namespace NUnit.ConsoleRunner -{ - public class ColorConsoleWriter : ExtendedTextWrapper - { - public bool _colorEnabled; - - /// - /// Construct a ColorConsoleWriter. - /// - public ColorConsoleWriter() : this(true) { } - - /// - /// Construct a ColorConsoleWriter. - /// - /// Flag indicating whether color should be enabled - public ColorConsoleWriter(bool colorEnabled) - : base(Console.Out) - { - _colorEnabled = colorEnabled; - } - - /// - /// Writes the value with the specified style. - /// - /// The style. - /// The value. - public override void Write(ColorStyle style, string value) - { - if (_colorEnabled) - using (new ColorConsole(style)) - { - Write(value); - } - else - Write(value); - } - - /// - /// Writes the value with the specified style. - /// - /// The style. - /// The value. - public override void WriteLine(ColorStyle style, string value) - { - if (_colorEnabled) - using (new ColorConsole(style)) - { - WriteLine(value); - } - else - WriteLine(value); - } - - /// - /// Writes the label and the option that goes with it. - /// - /// The label. - /// The option. - public override void WriteLabel(string label, object option) - { - WriteLabel(label, option, ColorStyle.Value); - } - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - public override void WriteLabelLine(string label, object option) - { - WriteLabelLine(label, option, ColorStyle.Value); - } - - /// - /// Writes the label and the option that goes with it and optionally writes a new line. - /// - /// The label. - /// The option. - /// The color to display the value with - public override void WriteLabel(string label, object option, ColorStyle valueStyle) - { - Guard.ArgumentNotNull(option, nameof(option)); - - Write(ColorStyle.Label, label); - Write(valueStyle, option.ToString() ?? string.Empty); - } - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - /// The color to display the value with - public override void WriteLabelLine(string label, object option, ColorStyle valueStyle) - { - WriteLabel(label, option, valueStyle); - WriteLine(); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ColorStyle.cs b/src/NUnitConsole/nunit4-netcore-console/ColorStyle.cs deleted file mode 100644 index ca369e3c7..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ColorStyle.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.ConsoleRunner -{ - /// - /// ColorStyle enumerates the various styles used in the console display - /// - public enum ColorStyle - { - /// - /// Color for headers - /// - Header, - /// - /// Color for sub-headers - /// - SubHeader, - /// - /// Color for each of the section headers - /// - SectionHeader, - /// - /// The default color for items that don't fit into the other categories - /// - Default, - /// - /// Test output - /// - Output, - /// - /// Color for help text - /// - Help, - /// - /// Color for labels - /// - Label, - /// - /// Color for values, usually go beside labels - /// - Value, - /// - /// Color for passed tests - /// - Pass, - /// - /// Color for failed tests - /// - Failure, - /// - /// Color for warnings, ignored or skipped tests - /// - Warning, - /// - /// Color for errors and exceptions - /// - Error - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ConsoleOptions.cs b/src/NUnitConsole/nunit4-netcore-console/ConsoleOptions.cs deleted file mode 100644 index 66d60717d..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ConsoleOptions.cs +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using NUnit.Engine; - -namespace NUnit.ConsoleRunner.Options -{ - /// - /// ConsoleOptions encapsulates the option settings for - /// the nunit4-console program. The class inherits from the Mono - /// Options class and provides a central location - /// for defining and parsing options. - /// - public class ConsoleOptions : OptionSet - { - private static readonly string CURRENT_DIRECTORY_ON_ENTRY = Directory.GetCurrentDirectory(); - - /// - /// An abstraction of the file system - /// - protected readonly IFileSystem _fileSystem; - - internal ConsoleOptions(IDefaultOptionsProvider defaultOptionsProvider, IFileSystem fileSystem, params string[] args) - { - // Apply default options - if (defaultOptionsProvider == null) throw new ArgumentNullException(nameof(defaultOptionsProvider)); - TeamCity = defaultOptionsProvider.TeamCity; - _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - - ConfigureOptions(); - if (args != null) - Parse(args); - } - - // Action to Perform - - public bool Explore { get; private set; } - - public bool ShowHelp { get; private set; } - - public bool ShowVersion { get; private set; } - - // Select tests - - public IList InputFiles { get; } = new List(); - - public IList TestList { get; } = new List(); - - public IDictionary TestParameters { get; } = new Dictionary(); - - public string? WhereClause { get; private set; } - - [MemberNotNullWhen(true, nameof(WhereClause))] - public bool WhereClauseSpecified { get { return WhereClause != null; } } - - public int DefaultTestCaseTimeout { get; private set; } = -1; - public bool DefaultTestCaseTimeoutSpecified { get { return DefaultTestCaseTimeout >= 0; } } - - public int RandomSeed { get; private set; } = -1; - public bool RandomSeedSpecified { get { return RandomSeed >= 0; } } - - public string? DefaultTestNamePattern { get; private set; } - public int NumberOfTestWorkers { get; private set; } = -1; - public bool NumberOfTestWorkersSpecified { get { return NumberOfTestWorkers >= 0; } } - - public bool StopOnError { get; private set; } - - public bool WaitBeforeExit { get; private set; } - - // Output Control - - public string? ConsoleEncoding { get; private set; } - - public bool NoHeader { get; private set; } - - public bool NoColor { get; private set; } - - public bool TeamCity { get; private set; } - - public string? OutFile { get; private set; } - - [MemberNotNullWhen(true, nameof(OutFile))] - public bool OutFileSpecified { get { return OutFile != null; } } - - public string? DisplayTestLabels { get; private set; } - - private string? workDirectory = null; - public string WorkDirectory - { - get { return workDirectory ?? CURRENT_DIRECTORY_ON_ENTRY; } - } - public bool WorkDirectorySpecified { get { return workDirectory != null; } } - - public string? InternalTraceLevel { get; private set; } - - [MemberNotNullWhen(true, nameof(InternalTraceLevel))] - public bool InternalTraceLevelSpecified { get { return InternalTraceLevel != null; } } - - private readonly List resultOutputSpecifications = new List(); - public IList ResultOutputSpecifications - { - get - { - if (NoResultSpecified) - return new OutputSpecification[0]; - - if (resultOutputSpecifications.Count == 0) - resultOutputSpecifications.Add( - new OutputSpecification("TestResult.xml", CURRENT_DIRECTORY_ON_ENTRY)); - - return resultOutputSpecifications; - } - } - - public bool NoResultSpecified { get; private set; } - - public IList ExploreOutputSpecifications { get; } = new List(); - - public string? ActiveConfig { get; private set; } - - [MemberNotNullWhen(true, nameof(ActiveConfig))] - public bool ActiveConfigSpecified { get { return ActiveConfig != null; } } - - // How to Run Tests - - public string? RuntimeFramework { get; private set; } - - [MemberNotNullWhen(true, nameof(RuntimeFramework))] - public bool RuntimeFrameworkSpecified { get { return RuntimeFramework != null; } } - - public string? ConfigurationFile { get; private set; } - - public bool RunAsX86 { get; private set; } - - public bool DisposeRunners { get; private set; } - - public bool ShadowCopyFiles { get; private set; } - - public bool LoadUserProfile { get; private set; } - - public bool SkipNonTestAssemblies { get; private set; } - - private int _maxAgents = -1; - public int MaxAgents { get { return _maxAgents; } } - public bool MaxAgentsSpecified { get { return _maxAgents >= 0; } } - - public bool DebugTests { get; private set; } - - public bool DebugAgent { get; private set; } - - public bool ListExtensions { get; private set; } - - public bool PauseBeforeRun { get; private set; } - - public string? PrincipalPolicy { get; private set; } - - public IList WarningMessages { get; } = new List(); - - public IList ErrorMessages { get; } = new List(); - - private void ConfigureOptions() - { - var parser = new OptionParser(s => ErrorMessages.Add(s)); - - // NOTE: The order in which patterns are added - // determines the display order for the help. - - this.Add("test=", "Comma-separated list of {NAMES} of tests to run or explore. This option may be repeated.", - v => ((List)TestList).AddRange(TestNameParser.Parse(parser.RequiredValue(v, "--test")))); - - this.Add("testlist=", "File {PATH} containing a list of tests to run, one per line. This option may be repeated.", - v => - { - string testListFile = parser.RequiredValue(v, "--testlist"); - - var fullTestListPath = ExpandToFullPath(testListFile); - - if (!File.Exists(fullTestListPath)) - ErrorMessages.Add("Unable to locate file: " + testListFile); - else - { - try - { - using (var rdr = new StreamReader(fullTestListPath)) - { - while (!rdr.EndOfStream) - { - var line = rdr.ReadLine()?.Trim(); - - if (!string.IsNullOrEmpty(line) && line[0] != '#') - ((List)TestList).Add(line); - } - } - } - catch (IOException) - { - ErrorMessages.Add("Unable to read file: " + testListFile); - } - } - }); - - this.Add("where=", "Test selection {EXPRESSION} indicating what tests will be run. See description below.", - v => WhereClause = parser.RequiredValue(v, "--where")); - - this.Add("param|p=", "Followed by a key-value pair separated by an equals sign. Test code can access the value by name. This option may be repeated.", - v => - { - var valuePair = parser.RequiredKeyValue(parser.RequiredValue(v, "--param")); - if (valuePair.HasValue) - { - TestParameters[valuePair.Value.Key] = valuePair.Value.Value; - } - }); - - this.Add("testCaseTimeout=", "Set timeout for each test case in {MILLISECONDS}. May be overridden with TimeoutAttribute.", - v => DefaultTestCaseTimeout = parser.RequiredInt(v, "--testCaseTimeout")); - - this.Add("seed=", "Set the random {SEED} used to generate test cases.", - v => RandomSeed = parser.RequiredInt(v, "--seed")); - - this.Add("workers=", "Specify the {NUMBER} of worker threads to be used in running tests. If not specified, defaults to 2 or the number of processors, whichever is greater.", - v => NumberOfTestWorkers = parser.RequiredInt(v, "--workers")); - - this.Add("stoponerror", "Stop run immediately upon any test failure or error.", - v => StopOnError = !string.IsNullOrEmpty(v)); - - this.Add("wait", "Wait for input before closing console window.", - v => WaitBeforeExit = !string.IsNullOrEmpty(v)); - - // Output Control - this.Add("work=", "{PATH} of the directory to use for output files. If not specified, defaults to the current directory.", - v => workDirectory = parser.RequiredValue(v, "--work")); - - this.Add("output|out=", "File {PATH} to contain text output from the tests.", - v => OutFile = parser.RequiredValue(v, "--output")); - - this.Add("result=", "An output {SPEC} for saving the test results.\nThis option may be repeated.", - v => - { - var spec = parser.ResolveOutputSpecification(parser.RequiredValue(v, "--resultxml"), resultOutputSpecifications, _fileSystem, CURRENT_DIRECTORY_ON_ENTRY); - if (spec != null) resultOutputSpecifications.Add(spec); - }); - - this.Add("explore:", "Display or save test info rather than running tests. Optionally provide an output {SPEC} for saving the test info. This option may be repeated.", v => - { - Explore = true; - var spec = parser.ResolveOutputSpecification(v, ExploreOutputSpecifications, _fileSystem, CURRENT_DIRECTORY_ON_ENTRY); - if (spec != null) ExploreOutputSpecifications.Add(spec); - }); - - this.Add("noresult", "Don't save any test results.", - v => NoResultSpecified = !string.IsNullOrEmpty(v)); - - this.Add("labels=", "Specify whether to write test case names to the output. Values: Off (Default), On, OnOutput, Before, After, BeforeAndAfter. On is currently an alias for OnOutput, but is subject to change.", - v => { - DisplayTestLabels = parser.RequiredValue(v, "--labels", "Off", "On", "OnOutput", "Before", "After", "BeforeAndAfter"); - }); - - this.Add("test-name-format=", "Non-standard naming pattern to use in generating test names.", - v => DefaultTestNamePattern = parser.RequiredValue(v, "--test-name-format")); - - this.Add("trace=", "Set internal trace {LEVEL}.\nValues: Off, Error, Warning, Info, Verbose (Debug)", - v => InternalTraceLevel = parser.RequiredValue(v, "--trace", "Off", "Error", "Warning", "Info", "Verbose", "Debug")); - - this.Add("teamcity", "Turns on use of TeamCity service messages. TeamCity engine extension is required.", - v => TeamCity = !string.IsNullOrEmpty(v)); - - this.Add("noheader|noh", "Suppress display of program information at start of run.", - v => NoHeader = !string.IsNullOrEmpty(v)); - - this.Add("nocolor|noc", "Displays console output without color.", - v => NoColor = !string.IsNullOrEmpty(v)); - - this.Add("help|h", "Display this message and exit.", - v => ShowHelp = !string.IsNullOrEmpty(v)); - - this.Add("version|V", "Display the header and exit.", - v => ShowVersion = !string.IsNullOrEmpty(v)); - - this.Add("encoding=", "Specifies the encoding to use for Console standard output, for example utf-8, ascii, unicode.", - v => ConsoleEncoding = parser.RequiredValue(v, "--encoding")); - - // Default - this.Add("<>", v => - { - if (v.StartsWith("-") || v.StartsWith("/") && Path.DirectorySeparatorChar != '/') - ErrorMessages.Add("Invalid argument: " + v); - else - InputFiles.Add(v); - }); - - this.Add("config=", "{NAME} of a project configuration to load (e.g.: Debug).", - v => ActiveConfig = parser.RequiredValue(v, "--config")); - - this.AddNetFxOnlyOption("configfile=", "{NAME} of configuration file to use for this run.", - NetFxOnlyOption("configfile=", v => ConfigurationFile = parser.RequiredValue(v, "--configfile"))); - - // How to Run Tests - this.AddNetFxOnlyOption("framework=", "{FRAMEWORK} type/version to use for tests.\nExamples: mono, net-3.5, v4.0, 2.0, mono-4.0. If not specified, tests will run under the framework they are compiled with.", - NetFxOnlyOption("framework=", v => RuntimeFramework = parser.RequiredValue(v, "--framework"))); - - this.AddNetFxOnlyOption("x86", "Run tests in an x86 process on 64 bit systems", - NetFxOnlyOption("x86", v => RunAsX86 = !string.IsNullOrEmpty(v))); - - this.Add("dispose-runners", "Dispose each test runner after it has finished running its tests.", - v => DisposeRunners = !string.IsNullOrEmpty(v)); - - this.AddNetFxOnlyOption("shadowcopy", "Shadow copy test files", - NetFxOnlyOption("shadowcopy", v => ShadowCopyFiles = !string.IsNullOrEmpty(v))); - - this.AddNetFxOnlyOption("loaduserprofile", "Load user profile in test runner processes", - NetFxOnlyOption("loaduserprofile", v => LoadUserProfile = !string.IsNullOrEmpty(v))); - - this.Add("skipnontestassemblies", "Skip any non-test assemblies specified, without error.", - v => SkipNonTestAssemblies = !string.IsNullOrEmpty(v)); - - this.AddNetFxOnlyOption("agents=", "Specify the maximum {NUMBER} of test assembly agents to run at one time. If not specified, there is no limit.", - NetFxOnlyOption("agents=", v => _maxAgents = parser.RequiredInt(v, "--agents"))); - - this.AddNetFxOnlyOption("debug", "Launch debugger to debug tests.", - NetFxOnlyOption("debug", v => DebugTests = !string.IsNullOrEmpty(v))); - - this.AddNetFxOnlyOption("pause", "Pause before running to allow attaching a debugger.", - NetFxOnlyOption("pause", v => PauseBeforeRun = !string.IsNullOrEmpty(v))); - - this.Add("list-extensions", "List all extension points and the extensions for each.", - v => ListExtensions = !string.IsNullOrEmpty(v)); - - this.AddNetFxOnlyOption("set-principal-policy=", "Set PrincipalPolicy for the test domain.", - NetFxOnlyOption("set-principal-policy=", v => PrincipalPolicy = parser.RequiredValue(v, "--set-principal-policy", "UnauthenticatedPrincipal", "NoPrincipal", "WindowsPrincipal"))); - -#if DEBUG - this.AddNetFxOnlyOption("debug-agent", "Launch debugger in nunit-agent when it starts.", - NetFxOnlyOption("debug-agent", v => DebugAgent = !string.IsNullOrEmpty(v))); -#endif - } - - private void AddNetFxOnlyOption(string prototype, string description, Action action) - { -#if NETFRAMEWORK - var isHidden = false; -#else - var isHidden = true; -#endif - this.Add(prototype, description, action, isHidden); - } - - private Action NetFxOnlyOption(string optionName, Action action) - { -#if NETFRAMEWORK - return action; -#else - return s => ErrorMessages.Add($"The {optionName} option is not available on this platform."); -#endif - } - - private void CheckOptionCombinations() - { - // Add any validation of option combinations here - } - - private int _nesting = 0; - - public IEnumerable PreParse(IEnumerable args) - { - if (args == null) throw new ArgumentNullException("args"); - - if (++_nesting > 3) - { - ErrorMessages.Add("Arguments file nesting exceeds maximum depth of 3."); - --_nesting; - return args; - } - - var listArgs = new List(); - - foreach (var arg in args) - { - if (arg.Length == 0 || arg[0] != '@') - { - listArgs.Add(arg); - continue; - } - - var filename = arg.Substring(1, arg.Length - 1); - if (string.IsNullOrEmpty(filename)) - { - ErrorMessages.Add("You must include a file name after @."); - continue; - } - - if (!_fileSystem.FileExists(filename)) - { - ErrorMessages.Add("The file \"" + filename + "\" was not found."); - continue; - } - - try - { - listArgs.AddRange(PreParse(GetArgsFromFile(filename))); - } - catch (IOException ex) - { - ErrorMessages.Add("Error reading \"" + filename + "\": " + ex.Message); - } - } - - --_nesting; - return listArgs; - } - - private static readonly Regex ArgsRegex = new Regex(@"\G(""((""""|[^""])+)""|(\S+)) *", RegexOptions.Compiled | RegexOptions.CultureInvariant); - - // Get args from a string of args - internal static IEnumerable GetArgs(string commandLine) - { - var ms = ArgsRegex.Matches(commandLine); - foreach (Match m in ms) - yield return Regex.Replace(m.Groups[2].Success ? m.Groups[2].Value : m.Groups[4].Value, @"""""", @""""); - } - - // Get args from an included file - private IEnumerable GetArgsFromFile(string filename) - { - var sb = new StringBuilder(); - - foreach (var line in _fileSystem.ReadLines(filename)) - { - if (!string.IsNullOrEmpty(line) && line[0] != '#' && line.Trim().Length > 0) - { - if (sb.Length > 0) - sb.Append(' '); - sb.Append(line); - } - } - - return GetArgs(sb.ToString()); - } - - private string? ExpandToFullPath(string path) - { - if (path == null) return null; - - return Path.GetFullPath(path); - } - - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ConsoleRunner.cs b/src/NUnitConsole/nunit4-netcore-console/ConsoleRunner.cs deleted file mode 100644 index 581f87692..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ConsoleRunner.cs +++ /dev/null @@ -1,529 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; -using System.IO; -using System.Xml; -using NUnit.Common; -using NUnit.ConsoleRunner.Utilities; -using NUnit.ConsoleRunner.Options; -using NUnit.Engine; -using NUnit.Engine.Extensibility; -using System.Runtime.InteropServices; -using System.Text; - -namespace NUnit.ConsoleRunner -{ - /// - /// ConsoleRunner provides the nunit4-console text-based - /// user interface, running the tests and reporting the results. - /// - public class ConsoleRunner - { - // Some operating systems truncate the return code to 8 bits, which - // only allows us a maximum of 127 in the positive range. We limit - // ourselves so as to stay in that range. - private const int MAXIMUM_RETURN_CODE_ALLOWED = 100; // In case we are running on Unix - - private const string EVENT_LISTENER_EXTENSION_PATH = "/NUnit/Engine/TypeExtensions/ITestEventListener"; - private const string TEAMCITY_EVENT_LISTENER = "NUnit.Engine.Listeners.TeamCityEventListener"; - - private const string INDENT4 = " "; - private const string INDENT6 = " "; - private const string INDENT8 = " "; - - public static readonly int OK = 0; - public static readonly int INVALID_ARG = -1; - public static readonly int INVALID_ASSEMBLY = -2; - //public static readonly int FIXTURE_NOT_FOUND = -3; //No longer in use - public static readonly int INVALID_TEST_FIXTURE = -4; - //public static readonly int UNLOAD_ERROR = -5; //No longer in use - public static readonly int UNEXPECTED_ERROR = -100; - - private readonly ITestEngine _engine; - private readonly ConsoleOptions _options; - private readonly IResultService _resultService; - private readonly ITestFilterService _filterService; - private readonly IExtensionService _extensionService; - - private readonly ExtendedTextWriter _outWriter; - - private readonly string _workDirectory; - - public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWriter writer) - { - _engine = engine; - _options = options; - _outWriter = writer; - - _workDirectory = options.WorkDirectory ?? Directory.GetCurrentDirectory(); - - if (!Directory.Exists(_workDirectory)) - Directory.CreateDirectory(_workDirectory); - - _resultService = _engine.Services.GetService(); - _filterService = _engine.Services.GetService(); - _extensionService = _engine.Services.GetService(); - - // TODO: Exit with error if any of the services are not found - - if (_options.TeamCity) - { - bool teamcityInstalled = false; - foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH)) - if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER) - break; - - if (!teamcityInstalled) throw new NUnitEngineException("Option --teamcity specified but the extension is not installed."); - } - - // Enable TeamCityEventListener immediately, before the console is redirected - _extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", _options.TeamCity); - } - - /// - /// Executes tests according to the provided command-line options. - /// - /// - public int Execute() - { - if (!VerifyEngineSupport(_options)) - return INVALID_ARG; - - DisplayRuntimeEnvironment(_outWriter); - - if (_options.ListExtensions) - DisplayExtensionList(); - - if (_options.InputFiles.Count == 0) - { - if (!_options.ListExtensions) - using (new ColorConsole(ColorStyle.Error)) - Console.Error.WriteLine("Error: no inputs specified"); - return ConsoleRunner.OK; - } - - DisplayTestFiles(); - - TestPackage package = MakeTestPackage(_options); - - // We display the filters at this point so that any exception message - // thrown by CreateTestFilter will be understandable. - DisplayTestFilters(); - - TestFilter filter = CreateTestFilter(_options); - - if (_options.Explore) - return ExploreTests(package, filter); - else - return RunTests(package, filter); - } - - private void DisplayTestFiles() - { - _outWriter.WriteLine(ColorStyle.SectionHeader, "Test Files"); - foreach (string file in _options.InputFiles) - _outWriter.WriteLine(ColorStyle.Default, INDENT4 + file); - _outWriter.WriteLine(); - } - - private int ExploreTests(TestPackage package, TestFilter filter) - { - XmlNode result; - - using (var runner = _engine.GetRunner(package)) - result = runner.Explore(filter); - - if (_options.ExploreOutputSpecifications.Count == 0) - { - _resultService.GetResultWriter("cases", null).WriteResultFile(result, Console.Out); - } - else - { - foreach (OutputSpecification spec in _options.ExploreOutputSpecifications) - { - _resultService.GetResultWriter(spec.Format, spec.Transform).WriteResultFile(result, spec.OutputPath); - _outWriter.WriteLine("Results ({0}) saved as {1}", spec.Format, spec.OutputPath); - } - } - - return ConsoleRunner.OK; - } - - private int RunTests(TestPackage package, TestFilter filter) - { - - var writer = new ColorConsoleWriter(!_options.NoColor); - - foreach (var spec in _options.ResultOutputSpecifications) - { - var outputPath = Path.Combine(_workDirectory, spec.OutputPath); - - IResultWriter resultWriter; - - try - { - resultWriter = GetResultWriter(spec); - } - catch (Exception ex) - { - throw new NUnitEngineException($"Error encountered in resolving output specification: {spec}", ex); - } - - try - { - var outputDirectory = Path.GetDirectoryName(outputPath)!; - Directory.CreateDirectory(outputDirectory); - } - catch (Exception ex) - { - writer.WriteLine(ColorStyle.Error, String.Format( - "The directory in --result {0} could not be created", - spec.OutputPath)); - writer.WriteLine(ColorStyle.Error, ExceptionHelper.BuildMessage(ex)); - return ConsoleRunner.UNEXPECTED_ERROR; - } - - try - { - resultWriter.CheckWritability(outputPath); - } - catch (Exception ex) - { - throw new NUnitEngineException( - String.Format( - "The path specified in --result {0} could not be written to", - spec.OutputPath), ex); - } - - } - - var labels = _options.DisplayTestLabels != null - ? _options.DisplayTestLabels.ToUpperInvariant() - : "ON"; - - XmlNode? result = null; - NUnitEngineUnloadException? unloadException = null; - NUnitEngineException? engineException = null; - - try - { - using (new SaveConsoleOutput()) - using (ITestRunner runner = _engine.GetRunner(package)) - using (var output = CreateOutputWriter()) - { - var eventHandler = new TestEventHandler(output, labels); - - result = runner.Run(eventHandler, filter); - } - } - catch (NUnitEngineUnloadException ex) - { - unloadException = ex; - } - catch (NUnitEngineException ex) - { - engineException = ex; - } - - if (result != null) - { - var reporter = new ResultReporter(result, writer, _options); - reporter.ReportResults(); - - foreach (var spec in _options.ResultOutputSpecifications) - { - var outputPath = Path.Combine(_workDirectory, spec.OutputPath); - GetResultWriter(spec).WriteResultFile(result, outputPath); - writer.WriteLine("Results ({0}) saved as {1}", spec.Format, spec.OutputPath); - } - - if (engineException != null) - { - writer.WriteLine(ColorStyle.Error, Environment.NewLine + ExceptionHelper.BuildMessage(engineException)); - return ConsoleRunner.UNEXPECTED_ERROR; - } - - if (unloadException != null) - { - writer.WriteLine(ColorStyle.Warning, Environment.NewLine + ExceptionHelper.BuildMessage(unloadException)); - } - - if (reporter.Summary.UnexpectedError) - return ConsoleRunner.UNEXPECTED_ERROR; - - if (reporter.Summary.InvalidAssemblies > 0) - return ConsoleRunner.INVALID_ASSEMBLY; - - if (reporter.Summary.InvalidTestFixtures > 0) - return ConsoleRunner.INVALID_TEST_FIXTURE; - - var failureCount = reporter.Summary.FailureCount + reporter.Summary.ErrorCount + reporter.Summary.InvalidCount; - return Math.Min(failureCount, MAXIMUM_RETURN_CODE_ALLOWED); - } - - // If we got here, it's because we had an exception, but check anyway - if (engineException != null) - { - writer.WriteLine(ColorStyle.Error, ExceptionHelper.BuildMessage(engineException)); - writer.WriteLine(); - writer.WriteLine(ColorStyle.Error, ExceptionHelper.BuildMessageAndStackTrace(engineException)); - } - - return ConsoleRunner.UNEXPECTED_ERROR; - } - - private void DisplayRuntimeEnvironment(ExtendedTextWriter OutWriter) - { - OutWriter.WriteLine(ColorStyle.SectionHeader, "Runtime Environment"); - OutWriter.WriteLabelLine(INDENT4 + "OS Version: ", GetOSVersion()); -#if NETFRAMEWORK - OutWriter.WriteLabelLine(INDENT4 + "Runtime: ", ".NET Framework CLR v" + Environment.Version.ToString()); -#else - OutWriter.WriteLabelLine(INDENT4 + "Runtime: ", RuntimeInformation.FrameworkDescription); -#endif - - - OutWriter.WriteLine(); - } - - private static string GetOSVersion() - { -#if NETFRAMEWORK - OperatingSystem os = Environment.OSVersion; - string osString = os.ToString(); - if (os.Platform == PlatformID.Unix) - { - IntPtr buf = Marshal.AllocHGlobal(8192); - if (uname(buf) == 0) - { - var unixVariant = Marshal.PtrToStringAnsi(buf); - if (unixVariant.Equals("Darwin")) - unixVariant = "MacOSX"; - - osString = string.Format("{0} {1} {2}", unixVariant, os.Version, os.ServicePack); - } - Marshal.FreeHGlobal(buf); - } - return osString; -#else - return RuntimeInformation.OSDescription; -#endif - } - - [DllImport("libc")] - static extern int uname(IntPtr buf); - - private void DisplayExtensionList() - { - _outWriter.WriteLine(ColorStyle.SectionHeader, "Installed Extensions"); - - foreach (var ep in _extensionService?.ExtensionPoints ?? new IExtensionPoint[0]) - { - _outWriter.WriteLabelLine(INDENT4 + "Extension Point: ", ep.Path); - foreach (var node in ep.Extensions) - { - _outWriter.Write(INDENT6 + "Extension: "); - _outWriter.Write(ColorStyle.Value, $"{node.TypeName}"); - if(node.TargetFramework != null) - _outWriter.Write(ColorStyle.Value, $"(.NET {node.TargetFramework?.FrameworkVersion})"); - _outWriter.WriteLine(node.Enabled ? "" : " (Disabled)"); - - _outWriter.Write(INDENT8 + "Version: "); - _outWriter.WriteLine(ColorStyle.Value, node.AssemblyVersion.ToString()); - - _outWriter.Write(INDENT8 + "Path: "); - _outWriter.WriteLine(ColorStyle.Value, node.AssemblyPath); - - foreach (var prop in node.PropertyNames) - { - _outWriter.Write(INDENT8 + prop + ":"); - foreach (var val in node.GetValues(prop)) - _outWriter.Write(ColorStyle.Value, " " + val); - _outWriter.WriteLine(); - } - } - } - - _outWriter.WriteLine(); - } - - private void DisplayTestFilters() - { - if (_options.TestList.Count > 0 || _options.WhereClauseSpecified) - { - _outWriter.WriteLine(ColorStyle.SectionHeader, "Test Filters"); - - if (_options.TestList.Count > 0) - foreach (string testName in _options.TestList) - _outWriter.WriteLabelLine(INDENT4 + "Test: ", testName); - - if (_options.WhereClauseSpecified) - _outWriter.WriteLabelLine(INDENT4 + "Where: ", _options.WhereClause.Trim()); - - _outWriter.WriteLine(); - } - } - - private ExtendedTextWriter CreateOutputWriter() - { - if (_options.OutFileSpecified) - { - var outStreamWriter = new StreamWriter(Path.Combine(_workDirectory, _options.OutFile)); - outStreamWriter.AutoFlush = true; - - return new ExtendedTextWrapper(outStreamWriter); - } - - return _outWriter; - } - - private IResultWriter GetResultWriter(OutputSpecification spec) - { - return _resultService.GetResultWriter(spec.Format, spec.Transform); - } - - // This is public static for ease of testing - public static TestPackage MakeTestPackage(ConsoleOptions options) - { - TestPackage package = new TestPackage(options.InputFiles); - - if (options.RuntimeFrameworkSpecified) - package.AddSetting(EnginePackageSettings.RequestedRuntimeFramework, options.RuntimeFramework); - - if (options.RunAsX86) - package.AddSetting(EnginePackageSettings.RunAsX86, true); - - // Console runner always sets DisposeRunners - //if (options.DisposeRunners) - package.AddSetting(EnginePackageSettings.DisposeRunners, true); - - if (options.ShadowCopyFiles) - package.AddSetting(EnginePackageSettings.ShadowCopyFiles, true); - - if (options.LoadUserProfile) - package.AddSetting(EnginePackageSettings.LoadUserProfile, true); - - if (options.SkipNonTestAssemblies) - package.AddSetting(EnginePackageSettings.SkipNonTestAssemblies, true); - - if (options.DefaultTestCaseTimeout >= 0) - package.AddSetting(FrameworkPackageSettings.DefaultTimeout, options.DefaultTestCaseTimeout); - - if (options.InternalTraceLevelSpecified) - package.AddSetting(FrameworkPackageSettings.InternalTraceLevel, options.InternalTraceLevel); - - if (options.ActiveConfigSpecified) - package.AddSetting(EnginePackageSettings.ActiveConfig, options.ActiveConfig); - - // Always add work directory, in case current directory is changed - var workDirectory = options.WorkDirectory ?? Directory.GetCurrentDirectory(); - package.AddSetting(FrameworkPackageSettings.WorkDirectory, workDirectory); - - if (options.StopOnError) - package.AddSetting(FrameworkPackageSettings.StopOnError, true); - - if (options.MaxAgentsSpecified) - package.AddSetting(EnginePackageSettings.MaxAgents, options.MaxAgents); - - if (options.NumberOfTestWorkersSpecified) - package.AddSetting(FrameworkPackageSettings.NumberOfTestWorkers, options.NumberOfTestWorkers); - - if (options.RandomSeedSpecified) - package.AddSetting(FrameworkPackageSettings.RandomSeed, options.RandomSeed); - - if (options.DebugTests) - { - package.AddSetting(FrameworkPackageSettings.DebugTests, true); - - if (!options.NumberOfTestWorkersSpecified) - package.AddSetting(FrameworkPackageSettings.NumberOfTestWorkers, 0); - } - - if (options.PauseBeforeRun) - package.AddSetting(FrameworkPackageSettings.PauseBeforeRun, true); - - if (options.PrincipalPolicy != null) - package.AddSetting(EnginePackageSettings.PrincipalPolicy, options.PrincipalPolicy); - -#if DEBUG - if (options.DebugAgent) - package.AddSetting(EnginePackageSettings.DebugAgent, true); - - //foreach (KeyValuePair entry in package.Settings) - // if (!(entry.Value is string || entry.Value is int || entry.Value is bool)) - // throw new Exception(string.Format("Package setting {0} is not a valid type", entry.Key)); -#endif - - if (options.DefaultTestNamePattern != null) - package.AddSetting(FrameworkPackageSettings.DefaultTestNamePattern, options.DefaultTestNamePattern); - - if (options.TestParameters.Count != 0) - AddTestParametersSetting(package, options.TestParameters); - - if (options.ConfigurationFile != null) - package.AddSetting(EnginePackageSettings.ConfigurationFile, options.ConfigurationFile); - - return package; - } - - /// - /// Sets test parameters, handling backwards compatibility. - /// - private static void AddTestParametersSetting(TestPackage testPackage, IDictionary testParameters) - { - testPackage.AddSetting(FrameworkPackageSettings.TestParametersDictionary, testParameters); - - if (testParameters.Count != 0) - { - // This cannot be changed without breaking backwards compatibility with old frameworks. - // Reserializes the way old frameworks understand, even if this runner's parsing is changed. - - var oldFrameworkSerializedParameters = new StringBuilder(); - foreach (var parameter in testParameters) - oldFrameworkSerializedParameters.Append(parameter.Key).Append('=').Append(parameter.Value).Append(';'); - - testPackage.AddSetting(FrameworkPackageSettings.TestParameters, oldFrameworkSerializedParameters.ToString(0, oldFrameworkSerializedParameters.Length - 1)); - } - } - - private TestFilter CreateTestFilter(ConsoleOptions options) - { - ITestFilterBuilder builder = _filterService.GetTestFilterBuilder(); - - foreach (string testName in options.TestList) - builder.AddTest(testName); - - if (options.WhereClauseSpecified) - builder.SelectWhere(options.WhereClause); - - return builder.GetFilter(); - } - - private bool VerifyEngineSupport(ConsoleOptions options) - { - foreach (var spec in options.ResultOutputSpecifications) - { - bool available = false; - - foreach (var format in _resultService.Formats) - { - if (spec.Format == format) - { - available = true; - break; - } - } - - if (!available) - { - Console.WriteLine("Unknown result format: {0}", spec.Format); - return false; - } - } - - return true; - } - } -} - diff --git a/src/NUnitConsole/nunit4-netcore-console/ConsoleTestResult.cs b/src/NUnitConsole/nunit4-netcore-console/ConsoleTestResult.cs deleted file mode 100644 index 19265bd28..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ConsoleTestResult.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml; - -namespace NUnit.ConsoleRunner -{ - /// - /// ConsoleTestResult represents the result of one test being - /// displayed in the console report. - /// - public class ConsoleTestResult - { - private static readonly char[] EOL_CHARS = new char[] { '\r', '\n' }; - - private readonly XmlNode _resultNode; - - /// - /// ConsoleTestResult represents the result of a test in the console runner. - /// - /// An XmlNode representing the result - /// Integer index used in the report listing - public ConsoleTestResult(XmlNode resultNode, int reportIndex) - { - _resultNode = resultNode; - ReportID = reportIndex.ToString(); - - Result = resultNode.GetAttribute("result"); - Label = resultNode.GetAttribute("label"); - Site = resultNode.GetAttribute("site"); - - Status = Label ?? Result ?? "Unkown"; - if (Status == "Failed" || Status == "Error") - if (Site == "SetUp" || Site == "TearDown") - Status = Site + " " + Status; - - FullName = resultNode.GetAttribute("fullname") ?? "Unknown"; - } - - public string? Result { get; private set; } - public string? Label { get; private set; } - public string? Site { get; private set; } - public string FullName { get; private set; } - - public string ReportID { get; private set; } - public string Status { get; private set; } - - public string? Message - { - get - { - return GetTrimmedInnerText(_resultNode.SelectSingleNode("failure/message")) ?? - GetTrimmedInnerText(_resultNode.SelectSingleNode("reason/message")); - } - } - - public string? StackTrace - { - get { return GetTrimmedInnerText(_resultNode.SelectSingleNode("failure/stack-trace")); } - } - - private List? _assertions; - public List Assertions - { - get - { - if (_assertions == null) - { - _assertions = new List(); - XmlNodeList? assertions = _resultNode.SelectNodes("assertions/assertion"); - if (assertions is not null) - foreach (XmlNode assertion in assertions) - Assertions.Add(new ConsoleTestResult.AssertionResult(assertion)); - } - - return _assertions; - } - } - - public void WriteResult(ExtendedTextWriter writer) - { - int numAsserts = Assertions.Count; - - if (numAsserts > 0) - { - int assertionCounter = 0; - string assertID = ReportID; - - foreach (var assertion in Assertions) - { - if (numAsserts > 1) - assertID = string.Format("{0}-{1}", ReportID, ++assertionCounter); - - WriteResult(writer, assertID, Status, FullName, assertion.Message, assertion.StackTrace); - } - } - else - WriteResult(writer, ReportID, Status, FullName, Message, StackTrace); - } - - private void WriteResult(ExtendedTextWriter writer, string reportID, string status, string fullName, string? message, string? stackTrace) - { - ColorStyle style = GetColorStyle(); - - writer.WriteLine(style, - string.Format("{0}) {1} : {2}", reportID, status, fullName)); - - if (!string.IsNullOrEmpty(message)) - writer.WriteLine(ColorStyle.Output, message); - - if (!string.IsNullOrEmpty(stackTrace)) - writer.WriteLine(ColorStyle.Output, stackTrace); - - writer.WriteLine(); // Skip after each item - } - - private ColorStyle GetColorStyle() - { - return Result == "Failed" - ? ColorStyle.Failure - : Result == "Warning" || Status == "Ignored" - ? ColorStyle.Warning - : ColorStyle.Output; - } - - private static string? GetTrimmedInnerText(XmlNode? node) - { - // In order to control the format, we trim any line-end chars - // from end of the strings we write and supply them via calls - // to WriteLine(). Newlines within the strings are retained. - return node != null - ? node.InnerText.TrimEnd(EOL_CHARS) - : null; - } - - public struct AssertionResult - { - public AssertionResult(XmlNode assertion) - { - Message = GetTrimmedInnerText(assertion.SelectSingleNode("message")); - StackTrace = GetTrimmedInnerText(assertion.SelectSingleNode("stack-trace")); - } - - public string? Message { get; private set; } - public string? StackTrace { get; private set; } - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWrapper.cs b/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWrapper.cs deleted file mode 100644 index 08892960a..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWrapper.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System.IO; -using System.Text; - -namespace NUnit.ConsoleRunner -{ - /// - /// ExtendedTextWrapper wraps a TextWriter and makes it - /// look like an ExtendedTextWriter. All style indications - /// are ignored. It's used when text is being written - /// to a file. - /// - public class ExtendedTextWrapper : ExtendedTextWriter - { - private readonly TextWriter _writer; - - public ExtendedTextWrapper(TextWriter writer) - { - _writer = writer; - } - - /// - /// Write a single char value - /// - public override void Write(char value) - { - _writer.Write(value); - } - - /// - /// Write a string value - /// - public override void Write(string? value) - { - _writer.Write(value); - } - - /// - /// Write a string value followed by a NewLine - /// - public override void WriteLine(string? value) - { - _writer.WriteLine(value); - } - - /// - /// Gets the encoding for this ExtendedTextWriter - /// - public override Encoding Encoding - { - get { return _writer.Encoding; } - } - - /// - /// Dispose the Extended TextWriter - /// - protected override void Dispose(bool disposing) - { - _writer.Dispose(); - } - - /// - /// Writes the value with the specified style. - /// - /// The style. - /// The value. - public override void Write(ColorStyle style, string value) - { - Write(value); - } - - /// - /// Writes the value with the specified style - /// - /// The style. - /// The value. - public override void WriteLine(ColorStyle style, string value) - { - WriteLine(value); - } - - /// - /// Writes the label and the option that goes with it. - /// - /// The label. - /// The option. - public override void WriteLabel(string label, object option) - { - Write(label); - Write(option.ToString()); - } - - /// - /// Writes the label and the option that goes with it. - /// - /// The label. - /// The option. - /// The color to display the value with - public override void WriteLabel(string label, object option, ColorStyle valueStyle) - { - WriteLabel(label, option); - } - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - public override void WriteLabelLine(string label, object option) - { - WriteLabel(label, option); - WriteLine(); - } - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - /// The color to display the value with - public override void WriteLabelLine(string label, object option, ColorStyle valueStyle) - { - WriteLabelLine(label, option); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWriter.cs b/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWriter.cs deleted file mode 100644 index 4dffe2cd3..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ExtendedTextWriter.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System.IO; - -namespace NUnit.ConsoleRunner -{ - /// - /// ExtendedTextWriter extends the TextWriter abstract class - /// to support displaying text in color. - /// - public abstract class ExtendedTextWriter : TextWriter - { - /// - /// Writes the value with the specified style. - /// - /// The style. - /// The value. - public abstract void Write(ColorStyle style, string value); - - /// - /// Writes the value with the specified style - /// - /// The style. - /// The value. - public abstract void WriteLine(ColorStyle style, string value); - - /// - /// Writes the label and the option that goes with it. - /// - /// The label. - /// The option. - public abstract void WriteLabel(string label, object option); - - /// - /// Writes the label and the option that goes with it. - /// - /// The label. - /// The option. - /// The color to display the value with - public abstract void WriteLabel(string label, object option, ColorStyle valueStyle); - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - public abstract void WriteLabelLine(string label, object option); - - /// - /// Writes the label and the option that goes with it followed by a new line. - /// - /// The label. - /// The option. - /// The color to display the value with - public abstract void WriteLabelLine(string label, object option, ColorStyle valueStyle); - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/FileSystem.cs b/src/NUnitConsole/nunit4-netcore-console/FileSystem.cs deleted file mode 100644 index c1f78f008..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/FileSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System.Collections.Generic; -using System.IO; -using System; - -namespace NUnit.ConsoleRunner -{ - internal class FileSystem : IFileSystem - { - public bool FileExists(string fileName) - { - if (fileName == null) throw new ArgumentNullException("fileName"); - - return File.Exists(fileName); - } - - public IEnumerable ReadLines(string fileName) - { - if (fileName == null) throw new ArgumentNullException("fileName"); - - using (var file = File.OpenText(fileName)) - { - string? line; - while ((line = file.ReadLine()) != null) - { - yield return line; - } - } - } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit4-netcore-console/FrameworkPackageSettings.cs b/src/NUnitConsole/nunit4-netcore-console/FrameworkPackageSettings.cs deleted file mode 100644 index 11ee9b630..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/FrameworkPackageSettings.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.ConsoleRunner -{ - /// - /// FrameworkPackageSettings is a static class containing constant values that - /// are used as keys in setting up a TestPackage. These values are used in - /// the framework, and set in the runner. Setting values may be a string, int or bool. - /// - public static class FrameworkPackageSettings - { - /// - /// Flag (bool) indicating whether tests are being debugged. - /// - public const string DebugTests = "DebugTests"; - - /// - /// Flag (bool) indicating whether to pause execution of tests to allow - /// the user to attach a debugger. - /// - public const string PauseBeforeRun = "PauseBeforeRun"; - - /// - /// The InternalTraceLevel for this run. Values are: "Default", - /// "Off", "Error", "Warning", "Info", "Debug", "Verbose". - /// Default is "Off". "Debug" and "Verbose" are synonyms. - /// - public const string InternalTraceLevel = "InternalTraceLevel"; - - /// - /// Full path of the directory to be used for work and result files. - /// This path is provided to tests by the framework TestContext. - /// - public const string WorkDirectory = "WorkDirectory"; - - /// - /// Integer value in milliseconds for the default timeout value - /// for test cases. If not specified, there is no timeout except - /// as specified by attributes on the tests themselves. - /// - public const string DefaultTimeout = "DefaultTimeout"; - - /// - /// A TextWriter to which the internal trace will be sent. - /// - public const string InternalTraceWriter = "InternalTraceWriter"; - - /// - /// A list of tests to be loaded. - /// - // TODO: Remove? - public const string LOAD = "LOAD"; - - /// - /// The number of test threads to run for the assembly. If set to - /// 1, a single queue is used. If set to 0, tests are executed - /// directly, without queuing. - /// - public const string NumberOfTestWorkers = "NumberOfTestWorkers"; - - /// - /// The random seed to be used for this assembly. If specified - /// as the value reported from a prior run, the framework should - /// generate identical random values for tests as were used for - /// that run, provided that no change has been made to the test - /// assembly. Default is a random value itself. - /// - public const string RandomSeed = "RandomSeed"; - - /// - /// If true, execution stops after the first error or failure. - /// - public const string StopOnError = "StopOnError"; - - /// - /// If true, use of the event queue is suppressed and test events are synchronous. - /// - public const string SynchronousEvents = "SynchronousEvents"; - - /// - /// The default naming pattern used in generating test names - /// - public const string DefaultTestNamePattern = "DefaultTestNamePattern"; - - /// - /// Parameters to be passed on to the tests, serialized to a single string which needs parsing. Obsoleted by ; kept for backward compatibility. - /// - public const string TestParameters = "TestParameters"; - - /// - /// Parameters to be passed on to the tests, already parsed into an IDictionary<string, string>. Replaces . - /// - public const string TestParametersDictionary = "TestParametersDictionary"; - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/DefaultOptionsProvider.cs b/src/NUnitConsole/nunit4-netcore-console/Options/DefaultOptionsProvider.cs deleted file mode 100644 index 72fd0c520..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/DefaultOptionsProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; - -namespace NUnit.ConsoleRunner.Options -{ - internal sealed class DefaultOptionsProvider : IDefaultOptionsProvider - { - private const string EnvironmentVariableTeamcityProjectName = "TEAMCITY_PROJECT_NAME"; - - public bool TeamCity - { - get - { - return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariableTeamcityProjectName)); - } - } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/IDefaultOptionsProvider.cs b/src/NUnitConsole/nunit4-netcore-console/Options/IDefaultOptionsProvider.cs deleted file mode 100644 index 543378afc..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/IDefaultOptionsProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -namespace NUnit.ConsoleRunner.Options -{ - internal interface IDefaultOptionsProvider - { - bool TeamCity { get; } - } -} \ No newline at end of file diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/OptionParser.cs b/src/NUnitConsole/nunit4-netcore-console/Options/OptionParser.cs deleted file mode 100644 index 40f4fb6be..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/OptionParser.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; - -namespace NUnit.ConsoleRunner.Options -{ - internal class OptionParser - { - private readonly Action _logError; - - public OptionParser(Action logError) - { - _logError = logError; - } - - /// - /// Case is ignored when val is compared to validValues. When a match is found, the - /// returned value will be in the canonical case from validValues. - /// - public string RequiredValue(string val, string option, params string[] validValues) - { - if (string.IsNullOrEmpty(val)) - _logError("Missing required value for option '" + option + "'."); - - bool isValid = true; - - if (validValues != null && validValues.Length > 0) - { - isValid = false; - - foreach (string valid in validValues) - if (string.Compare(valid, val, StringComparison.OrdinalIgnoreCase) == 0) - return valid; - - } - - if (!isValid) - _logError($"The value '{val}' is not valid for option '{option}'."); - - return val; - } - - public int RequiredInt(string val, string option) - { - if (string.IsNullOrEmpty(val)) - { - _logError("Missing required value for option '" + option + "'."); - return -1; - } - else - { - var success = int.TryParse(val, out var result); - if (!success) - { - _logError($"An int value was expected for option '{option}' but a value of '{val}' was used"); - return -1; - } - return result; - } - } - - public KeyValuePair? RequiredKeyValue(string testParameterSpecification) - { - var equalsIndex = testParameterSpecification.IndexOf("="); - - if (equalsIndex > 0 && equalsIndex < testParameterSpecification.Length - 1) - { - string name = testParameterSpecification.Substring(0, equalsIndex).Trim(); - if (name != string.Empty) - { - string value = testParameterSpecification.Substring(equalsIndex + 1).Trim(); - if (IsQuotedString(value)) - value = value.Substring(1, value.Length - 2); - - if (value != string.Empty) - return new KeyValuePair(name, value); - } - } - - _logError("Invalid format for test parameter. Use NAME=VALUE."); - return null; - } - - private bool IsQuotedString(string value) - { - var length = value.Length; - if (length > 2) - { - var quoteChar = value[0]; - if (quoteChar == '"' || quoteChar == '\'') - return value[length - 1] == quoteChar; - } - - return false; - } - - public OutputSpecification? ResolveOutputSpecification(string value, IList outputSpecifications, IFileSystem fileSystem, string currentDir) - { - if (value == null) - return null; - - OutputSpecification spec; - - try - { - spec = new OutputSpecification(value, currentDir); - } - catch (ArgumentException e) - { - _logError(e.Message); - return null; - } - - if (spec.Transform != null) - { - if (!fileSystem.FileExists(spec.Transform)) - { - _logError($"Transform {spec.Transform} could not be found."); - return null; - } - } - - return spec; - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/Options.cs b/src/NUnitConsole/nunit4-netcore-console/Options/Options.cs deleted file mode 100644 index 7cb932a4c..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/Options.cs +++ /dev/null @@ -1,2154 +0,0 @@ -// -// Options.cs -// -// Authors: -// Jonathan Pryor , -// Federico Di Gregorio -// Rolf Bjarne Kvinge -// -// Copyright (C) 2008 Novell (https://www.novell.com) -// Copyright (C) 2009 Federico Di Gregorio. -// Copyright (C) 2012 Xamarin Inc (https://www.xamarin.com) -// Copyright (C) 2017 Microsoft Corporation (https://www.microsoft.com) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -// Compile With: -// mcs -debug+ -r:System.Core Options.cs -o:Mono.Options.dll -t:library -// mcs -debug+ -d:LINQ -r:System.Core Options.cs -o:Mono.Options.dll -t:library -// -// The LINQ version just changes the implementation of -// OptionSet.Parse(IEnumerable), and confers no semantic changes. - -// -// A Getopt::Long-inspired option parsing library for C#. -// -// Mono.Options.OptionSet is built upon a key/value table, where the -// key is a option format string and the value is a delegate that is -// invoked when the format string is matched. -// -// Option format strings: -// Regex-like BNF Grammar: -// name: .+ -// type: [=:] -// sep: ( [^{}]+ | '{' .+ '}' )? -// aliases: ( name type sep ) ( '|' name type sep )* -// -// Each '|'-delimited name is an alias for the associated action. If the -// format string ends in a '=', it has a required value. If the format -// string ends in a ':', it has an optional value. If neither '=' or ':' -// is present, no value is supported. `=' or `:' need only be defined on one -// alias, but if they are provided on more than one they must be consistent. -// -// Each alias portion may also end with a "key/value separator", which is used -// to split option values if the option accepts > 1 value. If not specified, -// it defaults to '=' and ':'. If specified, it can be any character except -// '{' and '}' OR the *string* between '{' and '}'. If no separator should be -// used (i.e. the separate values should be distinct arguments), then "{}" -// should be used as the separator. -// -// Options are extracted either from the current option by looking for -// the option name followed by an '=' or ':', or is taken from the -// following option IFF: -// - The current option does not contain a '=' or a ':' -// - The current option requires a value (i.e. not a Option type of ':') -// -// The `name' used in the option format string does NOT include any leading -// option indicator, such as '-', '--', or '/'. All three of these are -// permitted/required on any named option. -// -// Option bundling is permitted so long as: -// - '-' is used to start the option group -// - all of the bundled options are a single character -// - at most one of the bundled options accepts a value, and the value -// provided starts from the next character to the end of the string. -// -// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' -// as '-Dname=value'. -// -// Option processing is disabled by specifying "--". All options after "--" -// are returned by OptionSet.Parse() unchanged and unprocessed. -// -// Unprocessed options are returned from OptionSet.Parse(). -// -// Examples: -// int verbose = 0; -// OptionSet p = new OptionSet () -// .Add ("v", v => ++verbose) -// .Add ("name=|value=", v => Console.WriteLine (v)); -// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); -// -// The above would parse the argument string array, and would invoke the -// lambda expression three times, setting `verbose' to 3 when complete. -// It would also print out "A" and "B" to standard output. -// The returned array would contain the string "extra". -// -// C# 3.0 collection initializers are supported and encouraged: -// var p = new OptionSet () { -// { "h|?|help", v => ShowHelp () }, -// }; -// -// System.ComponentModel.TypeConverter is also supported, allowing the use of -// custom data types in the callback type; TypeConverter.ConvertFromString() -// is used to convert the value option to an instance of the specified -// type: -// -// var p = new OptionSet () { -// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, -// }; -// -// Random other tidbits: -// - Boolean options (those w/o '=' or ':' in the option format string) -// are explicitly enabled if they are followed with '+', and explicitly -// disabled if they are followed with '-': -// string a = null; -// var p = new OptionSet () { -// { "a", s => a = s }, -// }; -// p.Parse (new string[]{"-a"}); // sets v != null -// p.Parse (new string[]{"-a+"}); // sets v != null -// p.Parse (new string[]{"-a-"}); // sets v == null -// - -// -// Mono.Options.CommandSet allows easily having separate commands and -// associated command options, allowing creation of a *suite* along the -// lines of **git**(1), **svn**(1), etc. -// -// CommandSet allows intermixing plain text strings for `--help` output, -// Option values -- as supported by OptionSet -- and Command instances, -// which have a name, optional help text, and an optional OptionSet. -// -// var suite = new CommandSet ("suite-name") { -// // Use strings and option values, as with OptionSet -// "usage: suite-name COMMAND [OPTIONS]+", -// { "v:", "verbosity", (int? v) => Verbosity = v.HasValue ? v.Value : Verbosity+1 }, -// // Commands may also be specified -// new Command ("command-name", "command help") { -// Options = new OptionSet {/*...*/}, -// Run = args => { /*...*/}, -// }, -// new MyCommandSubclass (), -// }; -// return suite.Run (new string[]{...}); -// -// CommandSet provides a `help` command, and forwards `help COMMAND` -// to the registered Command instance by invoking Command.Invoke() -// with `--help` as an option. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -#if PCL -using System.Reflection; -#else -using System.Runtime.Serialization; -using System.Security.Permissions; -#endif -using System.Text; -using System.Text.RegularExpressions; -using NUnit.Common; - - -#if LINQ -using System.Linq; -#endif - - -#if PCL -using MessageLocalizerConverter = System.Func; -#else -using MessageLocalizerConverter = System.Converter; -#endif - -namespace NUnit.ConsoleRunner.Options -{ - static class StringCoda - { - - public static IEnumerable WrappedLines(string self, params int[] widths) - { - IEnumerable w = widths; - return WrappedLines(self, w); - } - - public static IEnumerable WrappedLines(string self, IEnumerable widths) - { - if (widths == null) - throw new ArgumentNullException("widths"); - return CreateWrappedLinesIterator(self, widths); - } - - private static IEnumerable CreateWrappedLinesIterator(string self, IEnumerable widths) - { - if (string.IsNullOrEmpty(self)) - { - yield return string.Empty; - yield break; - } - using (IEnumerator ewidths = widths.GetEnumerator()) - { - bool? hw = null; - int width = GetNextWidth(ewidths, int.MaxValue, ref hw); - int start = 0, end; - do - { - end = GetLineEnd(start, width, self); - char c = self[end - 1]; - if (char.IsWhiteSpace(c)) - --end; - bool needContinuation = end != self.Length && !IsEolChar(c); - string continuation = ""; - if (needContinuation) - { - --end; - continuation = "-"; - } - string line = self.Substring(start, end - start) + continuation; - yield return line; - start = end; - if (char.IsWhiteSpace(c)) - ++start; - width = GetNextWidth(ewidths, width, ref hw); - } while (start < self.Length); - } - } - - private static int GetNextWidth(IEnumerator ewidths, int curWidth, ref bool? eValid) - { - if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) - { - curWidth = (eValid = ewidths.MoveNext()).Value ? ewidths.Current : curWidth; - // '.' is any character, - is for a continuation - const string minWidth = ".-"; - if (curWidth < minWidth.Length) - throw new ArgumentOutOfRangeException("widths", - string.Format("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); - return curWidth; - } - // no more elements, use the last element. - return curWidth; - } - - private static bool IsEolChar(char c) - { - return !char.IsLetterOrDigit(c); - } - - private static int GetLineEnd(int start, int length, string description) - { - int end = System.Math.Min(start + length, description.Length); - int sep = -1; - for (int i = start; i < end; ++i) - { - if (description[i] == '\n') - return i + 1; - if (IsEolChar(description[i])) - sep = i + 1; - } - if (sep == -1 || end == description.Length) - return end; - return sep; - } - } - - public class OptionValueCollection : IList, IList - { - readonly List values = new List(); - readonly OptionContext c; - - internal OptionValueCollection(OptionContext c) - { - this.c = c; - } - - #region ICollection - void ICollection.CopyTo(Array array, int index) { (values as ICollection).CopyTo(array, index); } - bool ICollection.IsSynchronized { get { return (values as ICollection).IsSynchronized; } } - object ICollection.SyncRoot { get { return (values as ICollection).SyncRoot; } } - #endregion - - #region ICollection - public void Add(string item) { values.Add(item); } - public void Clear() { values.Clear(); } - public bool Contains(string item) { return values.Contains(item); } - public void CopyTo(string[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); } - public bool Remove(string item) { return values.Remove(item); } - public int Count { get { return values.Count; } } - public bool IsReadOnly { get { return false; } } - #endregion - - #region IEnumerable - IEnumerator IEnumerable.GetEnumerator() { return values.GetEnumerator(); } - #endregion - - #region IEnumerable - public IEnumerator GetEnumerator() { return values.GetEnumerator(); } - #endregion - - #region IList - int IList.Add(object? value) { return (values as IList).Add(value); } - bool IList.Contains(object? value) { return (values as IList).Contains(value); } - int IList.IndexOf(object? value) { return (values as IList).IndexOf(value); } - void IList.Insert(int index, object? value) { (values as IList).Insert(index, value); } - void IList.Remove(object? value) { (values as IList).Remove(value); } - void IList.RemoveAt(int index) { (values as IList).RemoveAt(index); } - bool IList.IsFixedSize { get { return false; } } - object? IList.this[int index] { get { return this[index]; } set { (values as IList)[index] = value; } } - #endregion - - #region IList - public int IndexOf(string item) { return values.IndexOf(item); } - public void Insert(int index, string item) { values.Insert(index, item); } - public void RemoveAt(int index) { values.RemoveAt(index); } - - private void AssertValid(int index) - { - if (c.Option == null) - throw new InvalidOperationException("OptionContext.Option is null."); - if (index >= c.Option.MaxValueCount) - throw new ArgumentOutOfRangeException("index"); - if (c.Option.OptionValueType == OptionValueType.Required && - index >= values.Count) - throw new OptionException(string.Format( - c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName), - c.OptionName); - } - - [DisallowNull] - public string this[int index] - { - get - { - AssertValid(index); - return index >= values.Count ? string.Empty : values[index]; - } - set - { - values[index] = value; - } - } - #endregion - - public List ToList() - { - return new List(values); - } - - public string[] ToArray() - { - return values.ToArray(); - } - - public override string ToString() - { - return string.Join(", ", values.ToArray()); - } - } - - public class OptionContext - { - private Option? option; - private string? name; - private int index; - private readonly OptionSet set; - private readonly OptionValueCollection c; - - public OptionContext(OptionSet set) - { - this.set = set; - this.c = new OptionValueCollection(this); - } - - public Option? Option - { - get { return option; } - set { option = value; } - } - - public string? OptionName - { - get { return name; } - set { name = value; } - } - - public int OptionIndex - { - get { return index; } - set { index = value; } - } - - public OptionSet OptionSet - { - get { return set; } - } - - public OptionValueCollection OptionValues - { - get { return c; } - } - } - - public enum OptionValueType - { - None, - Optional, - Required, - } - - public abstract class Option - { - readonly string prototype; - readonly string? description; - readonly string[] names; - readonly OptionValueType type; - readonly int count; - string[]? separators; - readonly bool hidden; - - protected Option(string prototype, string? description) - : this(prototype, description, 1, false) - { - } - - protected Option(string prototype, string? description, int maxValueCount) - : this(prototype, description, maxValueCount, false) - { - } - - protected Option(string prototype, string? description, int maxValueCount, bool hidden) - { - if (prototype == null) - throw new ArgumentNullException("prototype"); - if (prototype.Length == 0) - throw new ArgumentException("Cannot be the empty string.", "prototype"); - if (maxValueCount < 0) - throw new ArgumentOutOfRangeException("maxValueCount"); - - this.prototype = prototype; - this.description = description; - this.count = maxValueCount; - this.names = (this is OptionSet.Category) - // append GetHashCode() so that "duplicate" categories have distinct - // names, e.g. adding multiple "" categories should be valid. - ? new[] { prototype + this.GetHashCode() } - : prototype.Split('|'); - - if (this is OptionSet.Category || this is CommandOption) - return; - - this.type = ParsePrototype(); - this.hidden = hidden; - - if (this.count == 0 && type != OptionValueType.None) - throw new ArgumentException( - "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + - "OptionValueType.Optional.", - "maxValueCount"); - if (this.type == OptionValueType.None && maxValueCount > 1) - throw new ArgumentException( - string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), - "maxValueCount"); - if (Array.IndexOf(names, "<>") >= 0 && - ((names.Length == 1 && this.type != OptionValueType.None) || - (names.Length > 1 && this.MaxValueCount > 1))) - throw new ArgumentException( - "The default option handler '<>' cannot require values.", - "prototype"); - } - - public string Prototype { get { return prototype; } } - public string? Description { get { return description; } } - public OptionValueType OptionValueType { get { return type; } } - public int MaxValueCount { get { return count; } } - public bool Hidden { get { return hidden; } } - - public string[] GetNames() - { - return (string[])names.Clone(); - } - - public string[] GetValueSeparators() - { - if (separators == null) - return Array.Empty(); - return (string[])separators.Clone(); - } - - protected static T Parse(string value, OptionContext c) - { - Type tt = typeof(T); -#if PCL - TypeInfo ti = tt.GetTypeInfo (); -#else - Type ti = tt; -#endif - bool nullable = - ti.IsValueType && - ti.IsGenericType && - !ti.IsGenericTypeDefinition && - ti.GetGenericTypeDefinition() == typeof(Nullable<>); -#if PCL - Type targetType = nullable ? tt.GenericTypeArguments [0] : tt; -#else - Type targetType = nullable ? tt.GetGenericArguments()[0] : tt; -#endif - T t = default(T)!; - try - { - if (value != null) - { -#if PCL - if (targetType.GetTypeInfo ().IsEnum) - t = (T) Enum.Parse (targetType, value, true); - else - t = (T) Convert.ChangeType (value, targetType); -#else - TypeConverter conv = TypeDescriptor.GetConverter(targetType); - t = (T)conv.ConvertFromString(value)!; -#endif - } - } - catch (Exception e) - { - throw new OptionException( - string.Format( - c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."), - value, targetType.Name, c.OptionName), - c.OptionName, e); - } - - return t; - } - - internal string[] Names { get { return names; } } - internal string[]? ValueSeparators { get { return separators; } } - - static readonly char[] NameTerminator = new char[] { '=', ':' }; - - private OptionValueType ParsePrototype() - { - char type = '\0'; - List seps = new List(); - for (int i = 0; i < names.Length; ++i) - { - string? name = names[i]; - if (name.Length == 0) - throw new ArgumentException("Empty option names are not supported.", "prototype"); - - int end = name.IndexOfAny(NameTerminator); - if (end == -1) - continue; - names[i] = name.Substring(0, end); - if (type == '\0' || type == name[end]) - type = name[end]; - else - throw new ArgumentException( - string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]), - "prototype"); - AddSeparators(name, end, seps); - } - - if (type == '\0') - return OptionValueType.None; - - if (count <= 1 && seps.Count != 0) - throw new ArgumentException( - string.Format("Cannot provide key/value separators for Options taking {0} value(s).", count), - "prototype"); - if (count > 1) - { - if (seps.Count == 0) - this.separators = new string[] { ":", "=" }; - else if (seps.Count == 1 && seps[0].Length == 0) - this.separators = null; - else - this.separators = seps.ToArray(); - } - - return type == '=' ? OptionValueType.Required : OptionValueType.Optional; - } - - private static void AddSeparators(string name, int end, ICollection seps) - { - int start = -1; - for (int i = end + 1; i < name.Length; ++i) - { - switch (name[i]) - { - case '{': - if (start != -1) - throw new ArgumentException( - string.Format("Ill-formed name/value separator found in \"{0}\".", name), - "prototype"); - start = i + 1; - break; - case '}': - if (start == -1) - throw new ArgumentException( - string.Format("Ill-formed name/value separator found in \"{0}\".", name), - "prototype"); - seps.Add(name.Substring(start, i - start)); - start = -1; - break; - default: - if (start == -1) - seps.Add(name[i].ToString()); - break; - } - } - if (start != -1) - throw new ArgumentException( - string.Format("Ill-formed name/value separator found in \"{0}\".", name), - "prototype"); - } - - public void Invoke(OptionContext c) - { - OnParseComplete(c); - c.OptionName = null; - c.Option = null; - c.OptionValues.Clear(); - } - - protected abstract void OnParseComplete(OptionContext c); - - internal void InvokeOnParseComplete(OptionContext c) - { - OnParseComplete(c); - } - - public override string ToString() - { - return Prototype; - } - } - - public abstract class ArgumentSource - { - - protected ArgumentSource() - { - } - - public abstract string[] GetNames(); - public abstract string Description { get; } - public abstract bool GetArguments(string value, [NotNullWhen(true)] out IEnumerable? replacement); - -#if !PCL || NETSTANDARD1_3 - public static IEnumerable GetArgumentsFromFile(string file) - { - return GetArguments(File.OpenText(file), true); - } -#endif - - public static IEnumerable GetArguments(TextReader reader) - { - return GetArguments(reader, false); - } - - // Cribbed from mcs/driver.cs:LoadArgs(string) - static IEnumerable GetArguments(TextReader reader, bool close) - { - try - { - StringBuilder arg = new StringBuilder(); - - string? line; - while ((line = reader.ReadLine()) != null) - { - int t = line.Length; - - for (int i = 0; i < t; i++) - { - char c = line[i]; - - if (c == '"' || c == '\'') - { - char end = c; - - for (i++; i < t; i++) - { - c = line[i]; - - if (c == end) - break; - arg.Append(c); - } - } - else if (c == ' ') - { - if (arg.Length > 0) - { - yield return arg.ToString(); - arg.Length = 0; - } - } - else - arg.Append(c); - } - if (arg.Length > 0) - { - yield return arg.ToString(); - arg.Length = 0; - } - } - } - finally - { - if (close) - reader.Dispose(); - } - } - } - -#if !PCL || NETSTANDARD1_3 - public class ResponseFileSource : ArgumentSource - { - - public override string[] GetNames() - { - return new string[] { "@file" }; - } - - public override string Description - { - get { return "Read response file for more options."; } - } - - public override bool GetArguments(string value, [NotNullWhen(true)] out IEnumerable? replacement) - { - if (string.IsNullOrEmpty(value) || !value.StartsWith("@")) - { - replacement = null; - return false; - } - replacement = ArgumentSource.GetArgumentsFromFile(value.Substring(1)); - return true; - } - } -#endif - -#if !PCL - [Serializable] -#endif - public class OptionException : Exception - { - private string? option; - - public OptionException() - { - } - - public OptionException(string message, string? optionName) - : base(message) - { - this.option = optionName; - } - - public OptionException(string message, string? optionName, Exception innerException) - : base(message, innerException) - { - this.option = optionName; - } - -#if !PCL - [Obsolete] - protected OptionException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - this.option = info.GetString("OptionName"); - } -#endif - - public string? OptionName - { - get { return this.option; } - } - -#if !PCL - [Obsolete] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("OptionName", option); - } -#endif - } - - public delegate void OptionAction(TKey key, TValue value); - - public class OptionSet : KeyedCollection - { - public OptionSet() - : this(null) - { - } - - public OptionSet(MessageLocalizerConverter? localizer) - : base(StringComparer.Ordinal, dictionaryCreationThreshold: 0) - { - this.roSources = new ReadOnlyCollection(sources); - this.localizer = localizer ?? - delegate (string f) { - return f; - }; - } - - MessageLocalizerConverter localizer; - - private new IDictionary Dictionary => base.Dictionary!; - - public MessageLocalizerConverter MessageLocalizer - { - get { return localizer; } - internal set { localizer = value; } - } - - readonly List sources = new List(); - readonly ReadOnlyCollection roSources; - - public ReadOnlyCollection ArgumentSources - { - get { return roSources; } - } - - - protected override string GetKeyForItem(Option item) - { - if (item == null) - throw new ArgumentNullException("option"); - if (item.Names != null && item.Names.Length > 0) - return item.Names[0]; - // This should never happen, as it's invalid for Option to be - // constructed w/o any names. - throw new InvalidOperationException("Option has no names!"); - } - - [Obsolete("Use KeyedCollection.this[string]")] - protected Option? GetOptionForName(string option) - { - if (option == null) - throw new ArgumentNullException("option"); - try - { - return base[option]; - } - catch (KeyNotFoundException) - { - return null; - } - } - - protected override void InsertItem(int index, Option item) - { - base.InsertItem(index, item); - AddImpl(item); - } - - protected override void RemoveItem(int index) - { - Option p = Items[index]; - base.RemoveItem(index); - // KeyedCollection.RemoveItem() handles the 0th item - for (int i = 1; i < p.Names.Length; ++i) - { - Dictionary.Remove(p.Names[i]); - } - } - - protected override void SetItem(int index, Option item) - { - base.SetItem(index, item); - AddImpl(item); - } - - private void AddImpl(Option option) - { - if (option == null) - throw new ArgumentNullException("option"); - List added = new List(option.Names.Length); - try - { - // KeyedCollection.InsertItem/SetItem handle the 0th name. - for (int i = 1; i < option.Names.Length; ++i) - { - Dictionary.Add(option.Names[i], option); - added.Add(option.Names[i]); - } - } - catch (Exception) - { - foreach (string name in added) - Dictionary.Remove(name); - throw; - } - } - - public OptionSet Add(string header) - { - if (header == null) - throw new ArgumentNullException("header"); - Add(new Category(header)); - return this; - } - - internal sealed class Category : Option - { - - // Prototype starts with '=' because this is an invalid prototype - // (see Option.ParsePrototype(), and thus it'll prevent Category - // instances from being accidentally used as normal options. - public Category(string description) - : base("=:Category:= " + description, description) - { - } - - protected override void OnParseComplete(OptionContext c) - { - throw new NotSupportedException("Category.OnParseComplete should not be invoked."); - } - } - - - public new OptionSet Add(Option option) - { - base.Add(option); - return this; - } - - sealed class ActionOption : Option - { - readonly Action action; - - public ActionOption(string prototype, string? description, int count, Action action) - : this(prototype, description, count, action, false) - { - } - - public ActionOption(string prototype, string? description, int count, Action action, bool hidden) - : base(prototype, description, count, hidden) - { - if (action == null) - throw new ArgumentNullException("action"); - this.action = action; - } - - protected override void OnParseComplete(OptionContext c) - { - action(c.OptionValues); - } - } - - public OptionSet Add(string prototype, Action action) - { - return Add(prototype, null, action); - } - - public OptionSet Add(string prototype, string? description, Action action) - { - return Add(prototype, description, action, false); - } - - public OptionSet Add(string prototype, string? description, Action action, bool hidden) - { - if (action == null) - throw new ArgumentNullException("action"); - Option p = new ActionOption(prototype, description, 1, - delegate (OptionValueCollection v) { action(v[0]); }, hidden); - base.Add(p); - return this; - } - - public OptionSet Add(string prototype, OptionAction action) - { - return Add(prototype, null, action); - } - - public OptionSet Add(string prototype, string? description, OptionAction action) - { - return Add(prototype, description, action, false); - } - - public OptionSet Add(string prototype, string? description, OptionAction action, bool hidden) - { - if (action == null) - throw new ArgumentNullException("action"); - Option p = new ActionOption(prototype, description, 2, - delegate (OptionValueCollection v) { action(v[0], v[1]); }, hidden); - base.Add(p); - return this; - } - - sealed class ActionOption : Option - { - readonly Action action; - - public ActionOption(string prototype, string? description, Action action) - : base(prototype, description, 1) - { - if (action == null) - throw new ArgumentNullException("action"); - this.action = action; - } - - protected override void OnParseComplete(OptionContext c) - { - action(Parse(c.OptionValues[0], c)); - } - } - - sealed class ActionOption : Option - { - readonly OptionAction action; - - public ActionOption(string prototype, string? description, OptionAction action) - : base(prototype, description, 2) - { - if (action == null) - throw new ArgumentNullException("action"); - this.action = action; - } - - protected override void OnParseComplete(OptionContext c) - { - action( - Parse(c.OptionValues[0], c), - Parse(c.OptionValues[1], c)); - } - } - - public OptionSet Add(string prototype, Action action) - { - return Add(prototype, null, action); - } - - public OptionSet Add(string prototype, string? description, Action action) - { - return Add(new ActionOption(prototype, description, action)); - } - - public OptionSet Add(string prototype, OptionAction action) - { - return Add(prototype, null, action); - } - - public OptionSet Add(string prototype, string? description, OptionAction action) - { - return Add(new ActionOption(prototype, description, action)); - } - - public OptionSet Add(ArgumentSource source) - { - if (source == null) - throw new ArgumentNullException("source"); - sources.Add(source); - return this; - } - - protected virtual OptionContext CreateOptionContext() - { - return new OptionContext(this); - } - - public List Parse(IEnumerable arguments) - { - if (arguments == null) - throw new ArgumentNullException("arguments"); - OptionContext c = CreateOptionContext(); - c.OptionIndex = -1; - bool process = true; - List unprocessed = new List(); - Option? def = Contains("<>") ? this["<>"] : null; - ArgumentEnumerator ae = new ArgumentEnumerator(arguments); - foreach (string argument in ae) - { - ++c.OptionIndex; - if (argument == "--") - { - process = false; - continue; - } - if (!process) - { - Unprocessed(unprocessed, def, c, argument); - continue; - } - if (AddSource(ae, argument)) - continue; - if (!Parse(argument, c)) - Unprocessed(unprocessed, def, c, argument); - } - if (c.Option != null) - c.Option.Invoke(c); - return unprocessed; - } - - class ArgumentEnumerator : IEnumerable - { - readonly List> sources = new List>(); - - public ArgumentEnumerator(IEnumerable arguments) - { - sources.Add(arguments.GetEnumerator()); - } - - public void Add(IEnumerable arguments) - { - sources.Add(arguments.GetEnumerator()); - } - - public IEnumerator GetEnumerator() - { - do - { - IEnumerator c = sources[sources.Count - 1]; - if (c.MoveNext()) - yield return c.Current; - else - { - c.Dispose(); - sources.RemoveAt(sources.Count - 1); - } - } while (sources.Count > 0); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - bool AddSource(ArgumentEnumerator ae, string argument) - { - foreach (ArgumentSource source in sources) - { - IEnumerable? replacement; - if (!source.GetArguments(argument, out replacement)) - continue; - ae.Add(replacement); - return true; - } - return false; - } - - private static bool Unprocessed(ICollection extra, Option? def, OptionContext c, string argument) - { - if (def == null) - { - extra.Add(argument); - return false; - } - c.OptionValues.Add(argument); - c.Option = def; - c.Option.Invoke(c); - return false; - } - - private readonly Regex ValueOption = new Regex( - @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); - - protected bool GetOptionParts(string argument, [NotNullWhen(true)] out string? flag, [NotNullWhen(true)] out string? name, out string? sep, out string? value) - { - if (argument == null) - throw new ArgumentNullException("argument"); - - flag = name = sep = value = null; - Match m = ValueOption.Match(argument); - if (!m.Success) - { - return false; - } - flag = m.Groups["flag"].Value; - name = m.Groups["name"].Value; - if (m.Groups["sep"].Success && m.Groups["value"].Success) - { - sep = m.Groups["sep"].Value; - value = m.Groups["value"].Value; - } - return true; - } - - protected virtual bool Parse(string argument, OptionContext c) - { - if (c.Option != null) - { - ParseValue(argument, c); - return true; - } - - string? f, n, s, v; - if (!GetOptionParts(argument, out f, out n, out s, out v)) - return false; - - Option p; - if (Contains(n)) - { - p = this[n]; - c.OptionName = f + n; - c.Option = p; - switch (p.OptionValueType) - { - case OptionValueType.None: - c.OptionValues.Add(n); - c.Option.Invoke(c); - break; - case OptionValueType.Optional: - case OptionValueType.Required: - ParseValue(v, c); - break; - } - return true; - } - // no match; is it a bool option? - if (ParseBool(argument, n, c)) - return true; - // is it a bundled option? - if (ParseBundledValue(f, string.Concat(n + s + v), c)) - return true; - - return false; - } - - private void ParseValue(string? option, OptionContext c) - { - Guard.OperationValid(c.Option != null, "OptionContext.Option != null"); - - if (option != null) - foreach (string o in c.Option.ValueSeparators != null - ? option.Split(c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) - : new string[] { option }) - { - c.OptionValues.Add(o); - } - if (c.OptionValues.Count == c.Option.MaxValueCount || - c.Option.OptionValueType == OptionValueType.Optional) - c.Option.Invoke(c); - else if (c.OptionValues.Count > c.Option.MaxValueCount) - { - throw new OptionException(localizer(string.Format( - "Error: Found {0} option values when expecting {1}.", - c.OptionValues.Count, c.Option.MaxValueCount)), - c.OptionName); - } - } - - private bool ParseBool(string option, string n, OptionContext c) - { - Option p; - string rn; - if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') && - Contains((rn = n.Substring(0, n.Length - 1)))) - { - p = this[rn]; - string v = n[n.Length - 1] == '+' ? option : string.Empty; - c.OptionName = option; - c.Option = p; - c.OptionValues.Add(v); - p.Invoke(c); - return true; - } - return false; - } - - private bool ParseBundledValue(string f, string n, OptionContext c) - { - if (f != "-") - return false; - for (int i = 0; i < n.Length; ++i) - { - Option p; - string opt = f + n[i].ToString(); - string rn = n[i].ToString(); - if (!Contains(rn)) - { - if (i == 0) - return false; - throw new OptionException(string.Format(localizer( - "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null); - } - p = this[rn]; - switch (p.OptionValueType) - { - case OptionValueType.None: - Invoke(c, opt, n, p); - break; - case OptionValueType.Optional: - case OptionValueType.Required: - { - string v = n.Substring(i + 1); - c.Option = p; - c.OptionName = opt; - ParseValue(v.Length != 0 ? v : null, c); - return true; - } - default: - throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType); - } - } - return true; - } - - private static void Invoke(OptionContext c, string name, string value, Option option) - { - c.OptionName = name; - c.Option = option; - c.OptionValues.Add(value); - option.Invoke(c); - } - - private const int OptionWidth = 29; - private const int Description_FirstWidth = 80 - OptionWidth; - private const int Description_RemWidth = 80 - OptionWidth - 2; - - static readonly string CommandHelpIndentStart = new string(' ', OptionWidth); - static readonly string CommandHelpIndentRemaining = new string(' ', OptionWidth + 2); - - public void WriteOptionDescriptions(TextWriter o) - { - foreach (Option p in this) - { - int written = 0; - - if (p.Hidden) - continue; - - Category? c = p as Category; - if (c != null) - { - WriteDescription(o, p.Description, "", 80, 80); - continue; - } - CommandOption? co = p as CommandOption; - if (co != null) - { - WriteCommandDescription(o, co.Command, co.CommandName); - continue; - } - - if (!WriteOptionPrototype(o, p, ref written)) - continue; - - if (written < OptionWidth) - o.Write(new string(' ', OptionWidth - written)); - else - { - o.WriteLine(); - o.Write(new string(' ', OptionWidth)); - } - - WriteDescription(o, p.Description, new string(' ', OptionWidth + 2), - Description_FirstWidth, Description_RemWidth); - } - - foreach (ArgumentSource s in sources) - { - string[] names = s.GetNames(); - if (names == null || names.Length == 0) - continue; - - int written = 0; - - Write(o, ref written, " "); - Write(o, ref written, names[0]); - for (int i = 1; i < names.Length; ++i) - { - Write(o, ref written, ", "); - Write(o, ref written, names[i]); - } - - if (written < OptionWidth) - o.Write(new string(' ', OptionWidth - written)); - else - { - o.WriteLine(); - o.Write(new string(' ', OptionWidth)); - } - - WriteDescription(o, s.Description, new string(' ', OptionWidth + 2), - Description_FirstWidth, Description_RemWidth); - } - } - - internal void WriteCommandDescription(TextWriter o, Command c, string? commandName) - { - var name = new string(' ', 8) + (commandName ?? c.Name); - if (name.Length < OptionWidth - 1) - { - WriteDescription(o, name + new string(' ', OptionWidth - name.Length) + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); - } - else - { - WriteDescription(o, name, "", 80, 80); - WriteDescription(o, CommandHelpIndentStart + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); - } - } - - void WriteDescription(TextWriter o, string? value, string prefix, int firstWidth, int remWidth) - { - bool indent = false; - foreach (string line in GetLines(localizer(GetDescription(value)), firstWidth, remWidth)) - { - if (indent) - o.Write(prefix); - o.WriteLine(line); - indent = true; - } - } - - bool WriteOptionPrototype(TextWriter o, Option p, ref int written) - { - string[] names = p.Names; - - int i = GetNextOptionIndex(names, 0); - if (i == names.Length) - return false; - - if (names[i].Length == 1) - { - Write(o, ref written, " -"); - Write(o, ref written, names[0]); - } - else - { - Write(o, ref written, " --"); - Write(o, ref written, names[0]); - } - - for (i = GetNextOptionIndex(names, i + 1); - i < names.Length; i = GetNextOptionIndex(names, i + 1)) - { - Write(o, ref written, ", "); - Write(o, ref written, names[i].Length == 1 ? "-" : "--"); - Write(o, ref written, names[i]); - } - - if (p.OptionValueType == OptionValueType.Optional || - p.OptionValueType == OptionValueType.Required) - { - if (p.OptionValueType == OptionValueType.Optional) - { - Write(o, ref written, localizer("[")); - } - Write(o, ref written, localizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description))); - string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 - ? p.ValueSeparators[0] - : " "; - for (int c = 1; c < p.MaxValueCount; ++c) - { - Write(o, ref written, localizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description))); - } - if (p.OptionValueType == OptionValueType.Optional) - { - Write(o, ref written, localizer("]")); - } - } - return true; - } - - static int GetNextOptionIndex(string[] names, int i) - { - while (i < names.Length && names[i] == "<>") - { - ++i; - } - return i; - } - - static void Write(TextWriter o, ref int n, string s) - { - n += s.Length; - o.Write(s); - } - - static string GetArgumentName(int index, int maxIndex, string? description) - { - var matches = Regex.Matches(description ?? "", @"(?<=(? 1 - if (maxIndex > 1 && parts.Length == 2 && - parts[0] == index.ToString(CultureInfo.InvariantCulture)) - { - argName = parts[1]; - } - } - - if (string.IsNullOrEmpty(argName)) - { - argName = maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); - } - return argName; - } - - private static string GetDescription(string? description) - { - if (description == null) - return string.Empty; - StringBuilder sb = new StringBuilder(description.Length); - int start = -1; - for (int i = 0; i < description.Length; ++i) - { - switch (description[i]) - { - case '{': - if (i == start) - { - sb.Append('{'); - start = -1; - } - else if (start < 0) - start = i + 1; - break; - case '}': - if (start < 0) - { - if ((i + 1) == description.Length || description[i + 1] != '}') - throw new InvalidOperationException("Invalid option description: " + description); - ++i; - sb.Append("}"); - } - else - { - sb.Append(description.Substring(start, i - start)); - start = -1; - } - break; - case ':': - if (start < 0) - goto default; - start = i + 1; - break; - default: - if (start < 0) - sb.Append(description[i]); - break; - } - } - return sb.ToString(); - } - - private static IEnumerable GetLines(string description, int firstWidth, int remWidth) - { - return StringCoda.WrappedLines(description, firstWidth, remWidth); - } - } - - public class Command - { - private CommandSet? commandSet; - - public string Name { get; } - public string? Help { get; } - - public OptionSet? Options { get; set; } - public Action>? Run { get; set; } - - public CommandSet CommandSet { get => commandSet.ShouldNotBeNull(); internal set => commandSet = value; } - - public Command(string name, string? help = null) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); - - Name = NormalizeCommandName(name); - Help = help; - } - - static string NormalizeCommandName(string name) - { - var value = new StringBuilder(name.Length); - var space = false; - for (int i = 0; i < name.Length; ++i) - { - if (!char.IsWhiteSpace(name, i)) - { - space = false; - value.Append(name[i]); - } - else if (!space) - { - space = true; - value.Append(' '); - } - } - return value.ToString(); - } - - public virtual int Invoke(IEnumerable arguments) - { - var rest = Options?.Parse(arguments) ?? arguments; - Run?.Invoke(rest); - return 0; - } - } - - class CommandOption : Option - { - public Command Command { get; } - public string CommandName { get; } - - // Prototype starts with '=' because this is an invalid prototype - // (see Option.ParsePrototype(), and thus it'll prevent Category - // instances from being accidentally used as normal options. - public CommandOption(Command command, string? commandName = null, bool hidden = false) - : base("=:Command:= " + (commandName ?? command?.Name), (commandName ?? command?.Name), maxValueCount: 0, hidden: hidden) - { - if (command == null) - throw new ArgumentNullException(nameof(command)); - Command = command; - CommandName = commandName ?? command.Name; - } - - protected override void OnParseComplete(OptionContext c) - { - throw new NotSupportedException("CommandOption.OnParseComplete should not be invoked."); - } - } - - class HelpOption : Option - { - readonly Option option; - readonly CommandSet commands; - - public HelpOption(CommandSet commands, Option d) - : base(d.Prototype, d.Description, d.MaxValueCount, d.Hidden) - { - this.commands = commands; - this.option = d; - } - - protected override void OnParseComplete(OptionContext c) - { - commands.showHelp = true; - - option?.InvokeOnParseComplete(c); - } - } - - class CommandOptionSet : OptionSet - { - readonly CommandSet commands; - - public CommandOptionSet(CommandSet commands, MessageLocalizerConverter? localizer) - : base(localizer) - { - this.commands = commands; - } - - protected override void SetItem(int index, Option item) - { - if (ShouldWrapOption(item)) - { - base.SetItem(index, new HelpOption(commands, item)); - return; - } - base.SetItem(index, item); - } - - bool ShouldWrapOption(Option item) - { - if (item == null) - return false; - var help = item as HelpOption; - if (help != null) - return false; - foreach (var n in item.Names) - { - if (n == "help") - return true; - } - return false; - } - - protected override void InsertItem(int index, Option item) - { - if (ShouldWrapOption(item)) - { - base.InsertItem(index, new HelpOption(commands, item)); - return; - } - base.InsertItem(index, item); - } - } - - public class CommandSet : KeyedCollection - { - readonly string suite; - - OptionSet options; - TextWriter outWriter; - TextWriter errorWriter; - - internal List? NestedCommandSets; - - internal HelpCommand? help; - - internal bool showHelp; - - internal OptionSet Options => options; - -#if !PCL || NETSTANDARD1_3 - public CommandSet(string suite, MessageLocalizerConverter? localizer = null) - : this(suite, Console.Out, Console.Error, localizer) - { - } -#endif - - public CommandSet(string suite, TextWriter output, TextWriter error, MessageLocalizerConverter? localizer = null) - { - if (suite == null) - throw new ArgumentNullException(nameof(suite)); - if (output == null) - throw new ArgumentNullException(nameof(output)); - if (error == null) - throw new ArgumentNullException(nameof(error)); - - this.suite = suite; - options = new CommandOptionSet(this, localizer); - outWriter = output; - errorWriter = error; - } - - public string Suite => suite; - public TextWriter Out => outWriter; - public TextWriter Error => errorWriter; - public MessageLocalizerConverter MessageLocalizer => options.MessageLocalizer; - - protected override string GetKeyForItem(Command item) - { - return item.Name; - } - - public new CommandSet Add(Command value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - AddCommand(value); - options.Add(new CommandOption(value)); - return this; - } - - void AddCommand(Command value) - { - if (value.CommandSet != null && value.CommandSet != this) - { - throw new ArgumentException("Command instances can only be added to a single CommandSet.", nameof(value)); - } - value.CommandSet = this; - if (value.Options != null) - { - value.Options.MessageLocalizer = options.MessageLocalizer; - } - - base.Add(value); - - help = help ?? value as HelpCommand; - } - - public CommandSet Add(string header) - { - options.Add(header); - return this; - } - - public CommandSet Add(Option option) - { - options.Add(option); - return this; - } - - public CommandSet Add(string prototype, Action action) - { - options.Add(prototype, action); - return this; - } - - public CommandSet Add(string prototype, string description, Action action) - { - options.Add(prototype, description, action); - return this; - } - - public CommandSet Add(string prototype, string description, Action action, bool hidden) - { - options.Add(prototype, description, action, hidden); - return this; - } - - public CommandSet Add(string prototype, OptionAction action) - { - options.Add(prototype, action); - return this; - } - - public CommandSet Add(string prototype, string description, OptionAction action) - { - options.Add(prototype, description, action); - return this; - } - - public CommandSet Add(string prototype, string description, OptionAction action, bool hidden) - { - options.Add(prototype, description, action, hidden); - return this; - } - - public CommandSet Add(string prototype, Action action) - { - options.Add(prototype, null, action); - return this; - } - - public CommandSet Add(string prototype, string description, Action action) - { - options.Add(prototype, description, action); - return this; - } - - public CommandSet Add(string prototype, OptionAction action) - { - options.Add(prototype, action); - return this; - } - - public CommandSet Add(string prototype, string description, OptionAction action) - { - options.Add(prototype, description, action); - return this; - } - - public CommandSet Add(ArgumentSource source) - { - options.Add(source); - return this; - } - - public CommandSet Add(CommandSet nestedCommands) - { - if (nestedCommands == null) - throw new ArgumentNullException(nameof(nestedCommands)); - - if (NestedCommandSets == null) - { - NestedCommandSets = new List(); - } - - if (!AlreadyAdded(nestedCommands)) - { - NestedCommandSets.Add(nestedCommands); - foreach (var o in nestedCommands.options) - { - if (o is CommandOption c) - { - options.Add(new CommandOption(c.Command, $"{nestedCommands.Suite} {c.CommandName}")); - } - else - { - options.Add(o); - } - } - } - - nestedCommands.options = this.options; - nestedCommands.outWriter = this.outWriter; - nestedCommands.errorWriter = this.errorWriter; - - return this; - } - - bool AlreadyAdded(CommandSet value) - { - if (value == this) - return true; - if (NestedCommandSets == null) - return false; - foreach (var nc in NestedCommandSets) - { - if (nc.AlreadyAdded(value)) - return true; - } - return false; - } - - public IEnumerable GetCompletions(string? prefix = null) - { - string rest; - ExtractToken(ref prefix, out rest); - - foreach (var command in this) - { - if (command.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - yield return command.Name; - } - } - - if (NestedCommandSets == null) - yield break; - - foreach (var subset in NestedCommandSets) - { - if (subset.Suite.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - foreach (var c in subset.GetCompletions(rest)) - { - yield return $"{subset.Suite} {c}"; - } - } - } - } - - static void ExtractToken([NotNull] ref string? input, out string rest) - { - rest = ""; - input = input ?? ""; - - int top = input.Length; - for (int i = 0; i < top; i++) - { - if (char.IsWhiteSpace(input[i])) - continue; - - for (int j = i; j < top; j++) - { - if (char.IsWhiteSpace(input[j])) - { - rest = input.Substring(j).Trim(); - input = input.Substring(i, j).Trim(); - return; - } - } - rest = ""; - if (i != 0) - input = input.Substring(i).Trim(); - return; - } - } - - public int Run(IEnumerable arguments) - { - if (arguments == null) - throw new ArgumentNullException(nameof(arguments)); - - this.showHelp = false; - if (help == null) - { - help = new HelpCommand(); - AddCommand(help); - } - Action setHelp = v => showHelp = v != null; - if (!options.Contains("help")) - { - options.Add("help", "", setHelp, hidden: true); - } - if (!options.Contains("?")) - { - options.Add("?", "", setHelp, hidden: true); - } - var extra = options.Parse(arguments); - if (extra.Count == 0) - { - if (showHelp) - { - return help.Invoke(extra); - } - Out.WriteLine(options.MessageLocalizer($"Use `{Suite} help` for usage.")); - return 1; - } - var command = GetCommand(extra); - if (command == null) - { - help.WriteUnknownCommand(extra[0]); - return 1; - } - if (showHelp) - { - if (command.Options?.Contains("help") ?? true) - { - extra.Add("--help"); - return command.Invoke(extra); - } - command.Options.WriteOptionDescriptions(Out); - return 0; - } - return command.Invoke(extra); - } - - internal Command? GetCommand(List extra) - { - return TryGetLocalCommand(extra) ?? TryGetNestedCommand(extra); - } - - Command? TryGetLocalCommand(List extra) - { - var name = extra[0]; - if (Contains(name)) - { - extra.RemoveAt(0); - return this[name]; - } - for (int i = 1; i < extra.Count; ++i) - { - name = name + " " + extra[i]; - if (!Contains(name)) - continue; - extra.RemoveRange(0, i + 1); - return this[name]; - } - return null; - } - - Command? TryGetNestedCommand(List extra) - { - if (NestedCommandSets == null) - return null; - - var nestedCommands = NestedCommandSets.Find(c => c.Suite == extra[0]); - if (nestedCommands == null) - return null; - - var extraCopy = new List(extra); - extraCopy.RemoveAt(0); - if (extraCopy.Count == 0) - return null; - - var command = nestedCommands.GetCommand(extraCopy); - if (command != null) - { - extra.Clear(); - extra.AddRange(extraCopy); - return command; - } - return null; - } - } - - public class HelpCommand : Command - { - public HelpCommand() - : base("help", help: "Show this message and exit") - { - } - - public override int Invoke(IEnumerable arguments) - { - var extra = new List(); - var _ = CommandSet.Options.MessageLocalizer; - if (extra.Count == 0) - { - CommandSet.Options.WriteOptionDescriptions(CommandSet.Out); - return 0; - } - var command = CommandSet.GetCommand(extra); - if (command == this || extra.Contains("--help")) - { - CommandSet.Out.WriteLine(_($"Usage: {CommandSet.Suite} COMMAND [OPTIONS]")); - CommandSet.Out.WriteLine(_($"Use `{CommandSet.Suite} help COMMAND` for help on a specific command.")); - CommandSet.Out.WriteLine(); - CommandSet.Out.WriteLine(_($"Available commands:")); - CommandSet.Out.WriteLine(); - var commands = GetCommands(); - commands.Sort((x, y) => string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase)); - foreach (var c in commands) - { - if (c.Key == "help") - { - continue; - } - CommandSet.Options.WriteCommandDescription(CommandSet.Out, c.Value, c.Key); - } - if (CommandSet.help != null) - CommandSet.Options.WriteCommandDescription(CommandSet.Out, CommandSet.help, "help"); - return 0; - } - if (command == null) - { - WriteUnknownCommand(extra[0]); - return 1; - } - if (command.Options != null) - { - command.Options.WriteOptionDescriptions(CommandSet.Out); - return 0; - } - return command.Invoke(new[] { "--help" }); - } - - List> GetCommands() - { - var commands = new List>(); - - var commandSet = CommandSet; - - foreach (var c in commandSet) - { - commands.Add(new KeyValuePair(c.Name, c)); - } - - if (commandSet.NestedCommandSets == null) - return commands; - - foreach (var nc in commandSet.NestedCommandSets) - { - AddNestedCommands(commands, "", nc); - } - - return commands; - } - - void AddNestedCommands(List> commands, string outer, CommandSet value) - { - foreach (var v in value) - { - commands.Add(new KeyValuePair($"{outer}{value.Suite} {v.Name}", v)); - } - if (value.NestedCommandSets == null) - return; - foreach (var nc in value.NestedCommandSets) - { - AddNestedCommands(commands, $"{outer}{value.Suite} ", nc); - } - } - - internal void WriteUnknownCommand(string unknownCommand) - { - var commandSet = CommandSet; - - commandSet.Error.WriteLine(commandSet.Options.MessageLocalizer($"{commandSet.Suite}: Unknown command: {unknownCommand}")); - commandSet.Error.WriteLine(commandSet.Options.MessageLocalizer($"{commandSet.Suite}: Use `{commandSet.Suite} help` for usage.")); - } - } -} - diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/OutputSpecification.cs b/src/NUnitConsole/nunit4-netcore-console/Options/OutputSpecification.cs deleted file mode 100644 index 24f03b8b5..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/OutputSpecification.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.IO; -using System.Text; - -namespace NUnit.ConsoleRunner.Options -{ - /// - /// OutputSpecification encapsulates a file output path and format - /// for use in saving the results of a run. - /// - public class OutputSpecification - { - /// - /// Construct an OutputSpecification from an option value. - /// - /// The option value string. - /// The folder containing the transform. - public OutputSpecification(string spec, string? transformFolder) - { - if (spec == null) - throw new ArgumentNullException(nameof(spec), "Output spec may not be null"); - - string[] parts = spec.Split(';'); - this.OutputPath = parts[0]; - - for (int i = 1; i < parts.Length; i++) - { - string[] opt = parts[i].Split('='); - - if (opt.Length != 2) - throw new ArgumentException($"Invalid output specification: {spec}"); - - switch (opt[0].Trim()) - { - case "format": - string fmt = opt[1].Trim(); - - if (this.Format != null && this.Format != fmt) - throw new ArgumentException( - string.Format("Conflicting format options: {0}", spec)); - - this.Format = fmt; - break; - - case "transform": - string val = opt[1].Trim(); - - if (this.Transform != null && this.Transform != val) - throw new ArgumentException( - string.Format("Conflicting transform options: {0}", spec)); - - if (this.Format != null && this.Format != "user") - throw new ArgumentException( - string.Format("Conflicting format options: {0}", spec)); - - this.Format = "user"; - this.Transform = Path.Combine(transformFolder ?? "", val); - break; - } - } - - if (Format == null) - Format = "nunit3"; - } - - /// - /// Gets the path to which output will be written - /// - public string OutputPath { get; private set; } - - /// - /// Gets the name of the format to be used - /// - public string Format { get; private set; } - - /// - /// Gets the file name of a transform to be applied - /// - public string? Transform { get; private set; } - - public override string ToString() - { - var sb = new StringBuilder($"OutputPath: {OutputPath}"); - if (Format != null) sb.Append($", Format: {Format}"); - if (Transform != null) sb.Append($", Transform: {Transform}"); - return sb.ToString(); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/Options/TestNameParser.cs b/src/NUnitConsole/nunit4-netcore-console/Options/TestNameParser.cs deleted file mode 100644 index 77840b686..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Options/TestNameParser.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System.Collections.Generic; - -namespace NUnit.ConsoleRunner.Options -{ - /// - /// TestNameParser is used to parse the arguments to the - /// -run option, separating testnames at the correct point. - /// - public class TestNameParser - { - /// - /// Parse the -run argument and return an array of argument - /// - /// argument - /// - public static string[] Parse(string argument) - { - List list = new List(); - - int index = 0; - while (index < argument.Length) - { - string name = GetTestName(argument, ref index); - if (name != null && name != string.Empty) - list.Add(name); - } - - return list.ToArray(); - } - - private static string GetTestName(string argument, ref int index) - { - int separator = GetSeparator(argument, index); - string result; - - if (separator >= 0) - { - result = argument.Substring(index, separator - index).Trim(); - index = separator + 1; - } - else - { - result = argument.Substring(index).Trim(); - index = argument.Length; - } - - return result; - } - - private static int GetSeparator(string argument, int index) - { - int nest = 0; - - while (index < argument.Length) - { - switch (argument[index]) - { - case ',': - if (nest == 0) - return index; - break; - - case '"': - while (++index < argument.Length && argument[index] != '"') - ; - break; - - case '(': - case '<': - nest++; - break; - - case ')': - case '>': - nest--; - break; - } - - index++; - } - - return -1; - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/Program.cs b/src/NUnitConsole/nunit4-netcore-console/Program.cs index 5103f372b..2311a4c1f 100644 --- a/src/NUnitConsole/nunit4-netcore-console/Program.cs +++ b/src/NUnitConsole/nunit4-netcore-console/Program.cs @@ -97,28 +97,6 @@ public static int Main(string[] args) return ConsoleRunner.INVALID_ARG; } -#if NETFRAMEWORK - if (Options.RuntimeFrameworkSpecified) - { - var availableRuntimeService = engine.Services.GetService(); - if (availableRuntimeService == null) - { - WriteErrorMessage("Unable to acquire AvailableRuntimeService from engine"); - return ConsoleRunner.UNEXPECTED_ERROR; - } - - bool runtimeAvailable = false; - foreach (var runtime in availableRuntimeService.AvailableRuntimes) - { - if (runtimeAvailable = runtime.Id == Options.RuntimeFramework) - break; - } - - if (!runtimeAvailable) - WriteErrorMessage("Unavailable runtime framework requested: " + Options.RuntimeFramework); - } -#endif - if (Options.WorkDirectory != null) engine.WorkDirectory = Options.WorkDirectory; @@ -263,7 +241,6 @@ private static void WriteHelpText() OutWriter.WriteLine(" extension NUnitProjectLoader is required. For Visual Studio projects"); OutWriter.WriteLine(" and solutions the engine extension VSProjectLoader is required."); OutWriter.WriteLine(); -#if NETCOREAPP OutWriter.WriteLine(ColorStyle.SectionHeader, "Limitations:"); OutWriter.WriteLine(" The NetCore Runner is primarily intended for use as a dotnet tool."); OutWriter.WriteLine(" When used in this way, a single assembly is usually being tested and"); @@ -285,7 +262,6 @@ private static void WriteHelpText() OutWriter.WriteLine(" --pause Used for debugging agents."); OutWriter.WriteLine(" --set-principal-policy Not available."); OutWriter.WriteLine(" --debug-agent No agents are used."); -#endif } } diff --git a/src/NUnitConsole/nunit4-netcore-console/ResultReporter.cs b/src/NUnitConsole/nunit4-netcore-console/ResultReporter.cs deleted file mode 100644 index 35893f5a2..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ResultReporter.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Xml; -using NUnit.ConsoleRunner.Options; -using NUnit.ConsoleRunner.Utilities; - -namespace NUnit.ConsoleRunner -{ - public class ResultReporter - { - public ResultReporter(XmlNode resultNode, ExtendedTextWriter writer, ConsoleOptions options) - { - ResultNode = resultNode; - Writer = writer; - Options = options; - - string? overallResult = resultNode.GetAttribute("result"); - if (overallResult == "Skipped") - OverallResult = "Warning"; - if (overallResult == null) - OverallResult = "Unknown"; - else - OverallResult = overallResult; - Summary = new ResultSummary(resultNode); - } - - public ResultSummary Summary { get; private set; } - - private int ReportIndex { get; set; } - private XmlNode ResultNode { get; set; } - private ExtendedTextWriter Writer { get; set; } - private ConsoleOptions Options { get; set; } - private string OverallResult { get; set; } - - /// - /// Reports the results to the console - /// - public void ReportResults() - { - Writer.WriteLine(); - - if (Summary.ExplicitCount + Summary.SkipCount + Summary.IgnoreCount > 0) - WriteNotRunReport(); - - if (OverallResult == "Failed" || Summary.WarningCount > 0) - WriteErrorsFailuresAndWarningsReport(); - - WriteRunSettingsReport(); - - WriteSummaryReport(); - } - - internal void WriteRunSettingsReport() - { - var firstSuite = ResultNode.SelectSingleNode("test-suite"); - if (firstSuite != null) - { - var settings = firstSuite.SelectNodes("settings/setting"); - - if (settings is not null && settings.Count > 0) - { - Writer.WriteLine(ColorStyle.SectionHeader, "Run Settings"); - - foreach (XmlNode node in settings) - WriteSettingsNode(node); - - Writer.WriteLine(); - } - } - } - - private void WriteSettingsNode(XmlNode node) - { - var items = node.SelectNodes("item"); - var name = node.GetAttribute("name"); - var val = node.GetAttribute("value") ?? string.Empty; - - if (items is null || items.Count == 0) - Writer.WriteLabelLine($" {name}:", $" |{val}|"); - else - { - Writer.WriteLabelLine($" {name}:", string.Empty); - - foreach (XmlNode item in items) - { - var key = item.GetAttribute("key"); - var value = item.GetAttribute("value"); - Writer.WriteLine(ColorStyle.Value, $" {key} -> |{value}|"); - } - } - } - - public void WriteSummaryReport() - { - const string INDENT4 = " "; - const string INDENT8 = " "; - - ColorStyle overall = OverallResult == "Passed" - ? ColorStyle.Pass - : OverallResult == "Failed" || OverallResult == "Unknown" - ? ColorStyle.Failure - : OverallResult == "Warning" - ? ColorStyle.Warning - : ColorStyle.Output; - - Writer.WriteLine(ColorStyle.SectionHeader, "Test Run Summary"); - Writer.WriteLabelLine(INDENT4 + "Overall result: ", OverallResult, overall); - - WriteSummaryCount(INDENT4 + "Test Count: ", Summary.TestCount); - WriteSummaryCount(", Pass: ", Summary.PassCount); - WriteSummaryCount(", Fail: ", Summary.FailedCount, ColorStyle.Failure); - WriteSummaryCount(", Warn: ", Summary.WarningCount, ColorStyle.Warning); - WriteSummaryCount(", Inconclusive: ", Summary.InconclusiveCount); - WriteSummaryCount(", Skip: ", Summary.TotalSkipCount); - Writer.WriteLine(); - - if (Summary.FailedCount > 0) - { - WriteSummaryCount(INDENT8 + "Failed Tests - Failures: ", Summary.FailureCount); - WriteSummaryCount(", Errors: ", Summary.ErrorCount, ColorStyle.Error); - WriteSummaryCount(", Invalid: ", Summary.InvalidCount); - Writer.WriteLine(); - } - if (Summary.TotalSkipCount > 0) - { - WriteSummaryCount(INDENT8 + "Skipped Tests - Ignored: ", Summary.IgnoreCount); - WriteSummaryCount(", Explicit: ", Summary.ExplicitCount); - WriteSummaryCount(", Other: ", Summary.SkipCount); - Writer.WriteLine(); - } - - var duration = ResultNode.GetAttribute("duration", 0.0); - var startTime = ResultNode.GetAttribute("start-time", DateTime.MinValue); - var endTime = ResultNode.GetAttribute("end-time", DateTime.MaxValue); - - Writer.WriteLabelLine(INDENT4 + "Start time: ", startTime.ToString("u")); - Writer.WriteLabelLine(INDENT4 + "End time: ", endTime.ToString("u")); - Writer.WriteLabelLine(INDENT4 + "Duration: ", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000} seconds", duration)); - Writer.WriteLine(); - } - - public void WriteErrorsFailuresAndWarningsReport() - { - ReportIndex = 0; - Writer.WriteLine(ColorStyle.SectionHeader, "Errors, Failures and Warnings"); - Writer.WriteLine(); - - WriteErrorsFailuresAndWarnings(ResultNode); - - if (Options.StopOnError) - { - Writer.WriteLine(ColorStyle.Failure, "Execution terminated after first error"); - Writer.WriteLine(); - } - } - - private void WriteErrorsFailuresAndWarnings(XmlNode resultNode) - { - string? resultState = resultNode.GetAttribute("result"); - - switch (resultNode.Name) - { - case "test-case": - if (resultState == "Failed" || resultState == "Warning") - new ConsoleTestResult(resultNode, ++ReportIndex).WriteResult(Writer); - return; - - case "test-run": - foreach (XmlNode childResult in resultNode.ChildNodes) - WriteErrorsFailuresAndWarnings(childResult); - break; - - case "test-suite": - if (resultState == "Failed" || resultState == "Warning") - { - var suiteType = resultNode.GetAttribute("type"); - if (suiteType == "Theory") - { - // Report failure of the entire theory and then go on - // to list the individual cases that failed - new ConsoleTestResult(resultNode, ++ReportIndex).WriteResult(Writer); - } - else - { - // Where did this happen? Default is in the current test. - var site = resultNode.GetAttribute("site"); - - // Correct a problem in some framework versions, whereby warnings and some failures - // are promulgated to the containing suite without setting the FailureSite. - if (site == null) - { - if (resultNode.SelectSingleNode("reason/message")?.InnerText == "One or more child tests had warnings" || - resultNode.SelectSingleNode("failure/message")?.InnerText == "One or more child tests had errors") - { - site = "Child"; - } - else - site = "Test"; - } - - // Only report errors in the current test method, setup or teardown - if (site == "SetUp" || site == "TearDown" || site == "Test") - new ConsoleTestResult(resultNode, ++ReportIndex).WriteResult(Writer); - - // Do not list individual "failed" tests after a one-time setup failure - if (site == "SetUp") return; - } - } - - foreach (XmlNode childResult in resultNode.ChildNodes) - WriteErrorsFailuresAndWarnings(childResult); - - break; - } - } - - public void WriteNotRunReport() - { - ReportIndex = 0; - Writer.WriteLine(ColorStyle.SectionHeader, "Tests Not Run"); - Writer.WriteLine(); - WriteNotRunResults(ResultNode); - } - - private void WriteNotRunResults(XmlNode resultNode) - { - switch (resultNode.Name) - { - case "test-case": - string? status = resultNode.GetAttribute("result"); - - if (status == "Skipped") - new ConsoleTestResult(resultNode, ++ReportIndex).WriteResult(Writer); - - break; - - case "test-suite": - case "test-run": - foreach (XmlNode childResult in resultNode.ChildNodes) - WriteNotRunResults(childResult); - - break; - } - } - - private void WriteSummaryCount(string label, int count) - { - Writer.WriteLabel(label, count.ToString(CultureInfo.CurrentUICulture)); - } - - private void WriteSummaryCount(string label, int count, ColorStyle color) - { - Writer.WriteLabel(label, count.ToString(CultureInfo.CurrentUICulture), count > 0 ? color : ColorStyle.Value); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/ResultSummary.cs b/src/NUnitConsole/nunit4-netcore-console/ResultSummary.cs deleted file mode 100644 index 73c68ab09..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/ResultSummary.cs +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Globalization; -using System.Xml; -using NUnit.ConsoleRunner.Utilities; - -namespace NUnit.ConsoleRunner -{ - /// - /// Summary description for ResultSummary. - /// - public class ResultSummary - { - public ResultSummary(XmlNode result) - { - if (result.Name != "test-run") - throw new InvalidOperationException("Expected as top-level element but was <" + result.Name + ">"); - - InitializeCounters(); - - Summarize(result, false); - } - - /// - /// Gets the number of test cases for which results - /// have been summarized. Any tests excluded by use of - /// Category or Explicit attributes are not counted. - /// - public int TestCount { get; private set; } - - /// - /// Returns the number of test cases actually run. - /// - public int RunCount - { - get { return PassCount + FailureCount + ErrorCount + InconclusiveCount; } - } - - /// - /// Returns the number of test cases not run for any reason. - /// - public int NotRunCount - { - get { return IgnoreCount + ExplicitCount + InvalidCount + SkipCount; } - } - - /// - /// Returns the number of failed test cases (including errors and invalid tests) - /// - public int FailedCount - { - get { return FailureCount + InvalidCount + ErrorCount; } - } - - public int WarningCount { get; private set; } - - /// - /// Returns the sum of skipped test cases, including ignored and explicit tests - /// - public int TotalSkipCount - { - get { return SkipCount + IgnoreCount + ExplicitCount; } - } - - /// - /// Gets the count of passed tests - /// - public int PassCount { get; private set; } - - /// - /// Gets the count of failed tests, excluding errors and invalid tests - /// - public int FailureCount { get; private set; } - - /// - /// Returns the number of test cases that had an error. - /// - public int ErrorCount { get; private set; } - - /// - /// Gets the count of inconclusive tests - /// - public int InconclusiveCount { get; private set; } - - /// - /// Returns the number of test cases that were not runnable - /// due to errors in the signature of the class or method. - /// Such tests are also counted as Errors. - /// - public int InvalidCount { get; private set; } - - /// - /// Gets the count of skipped tests, excluding ignored and explicit tests - /// - public int SkipCount { get; private set; } - - /// - /// Gets the count of ignored tests - /// - public int IgnoreCount { get; private set; } - - /// - /// Gets the count of tests not run because the are Explicit - /// - public int ExplicitCount { get; private set; } - - /// - /// Gets the count of invalid assemblies - /// - public int InvalidAssemblies { get; private set; } - - /// - /// An Unexpected error occurred - /// - public bool UnexpectedError { get; private set; } - - /// - /// Invalid test fixture(s) were found - /// - public int InvalidTestFixtures { get; private set; } - - private void InitializeCounters() - { - TestCount = 0; - PassCount = 0; - FailureCount = 0; - WarningCount = 0; - ErrorCount = 0; - InconclusiveCount = 0; - SkipCount = 0; - IgnoreCount = 0; - ExplicitCount = 0; - InvalidCount = 0; - InvalidAssemblies = 0; - } - - private void Summarize(XmlNode node, bool failedInFixtureTearDown) - { - string? type = node.GetAttribute("type"); - string? status = node.GetAttribute("result"); - string? label = node.GetAttribute("label"); - string? site = node.GetAttribute("site"); - - switch (node.Name) - { - case "test-case": - TestCount++; - - switch (status) - { - case "Passed": - if (failedInFixtureTearDown) - ErrorCount++; - else - PassCount++; - break; - case "Failed": - if (failedInFixtureTearDown) - ErrorCount++; - else if (label == null) - FailureCount++; - else if (label == "Invalid") - InvalidCount++; - else - ErrorCount++; - break; - case "Warning": - if (failedInFixtureTearDown) - ErrorCount++; - else - WarningCount++; - break; - case "Inconclusive": - if (failedInFixtureTearDown) - ErrorCount++; - else - InconclusiveCount++; - break; - case "Skipped": - if (label == "Ignored") - IgnoreCount++; - else if (label == "Explicit") - ExplicitCount++; - else - SkipCount++; - break; - default: - //SkipCount++; - break; - } - break; - - case "test-suite": - if (status == "Failed" && label == "Invalid") - { - if (type == "Assembly") InvalidAssemblies++; - else InvalidTestFixtures++; - } - if (type == "Assembly" && status == "Failed" && label == "Error") - { - InvalidAssemblies++; - UnexpectedError = true; - } - if ((type == "SetUpFixture" || type == "TestFixture") && status == "Failed" && label == "Error" && site == "TearDown") - { - failedInFixtureTearDown = true; - } - - Summarize(node.ChildNodes, failedInFixtureTearDown); - break; - - case "test-run": - Summarize(node.ChildNodes, failedInFixtureTearDown); - break; - } - } - - private void Summarize(XmlNodeList nodes, bool failedInFixtureTearDown) - { - foreach (XmlNode childResult in nodes) - Summarize(childResult, failedInFixtureTearDown); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/SafeAttributeAccess.cs b/src/NUnitConsole/nunit4-netcore-console/SafeAttributeAccess.cs deleted file mode 100644 index 6566ef87d..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/SafeAttributeAccess.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Globalization; -using System.Xml; - -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] - sealed class ExtensionAttribute : Attribute { } -} - -namespace NUnit.ConsoleRunner -{ - /// - /// SafeAttributeAccess provides an extension method for accessing XML attributes. - /// - public static class SafeAttributeAccess - { - /// - /// Gets the value of the given attribute. - /// - /// The result. - /// The name. - /// - public static string? GetAttribute(this XmlNode result, string name) - { - XmlAttribute? attr = result.Attributes?[name]; - - return attr == null ? null : attr.Value; - } - - /// - /// Gets the value of the given attribute as a double. - /// - /// The result. - /// The name. - /// The default value. - /// - public static double GetAttribute(this XmlNode result, string name, double defaultValue) - { - XmlAttribute? attr = result.Attributes?[name]; - - return attr == null - ? defaultValue - : double.Parse(attr.Value, System.Globalization.CultureInfo.InvariantCulture); - } - - /// - /// Gets the value of the given attribute as a DateTime. - /// - /// The result. - /// The name. - /// The default value. - /// - public static DateTime GetAttribute(this XmlNode result, string name, DateTime defaultValue) - { - string? dateStr = GetAttribute(result, name); - if (dateStr == null) - return defaultValue; - - DateTime date; - if (!DateTime.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces, out date)) - return defaultValue; - - return date; - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/TestEventHandler.cs b/src/NUnitConsole/nunit4-netcore-console/TestEventHandler.cs deleted file mode 100644 index 871f5e260..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/TestEventHandler.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.Xml; -using NUnit.Engine; - -namespace NUnit.ConsoleRunner -{ - /// - /// TestEventHandler processes events from the running - /// test for the console runner. - /// -#if NETFRAMEWORK - public class TestEventHandler : MarshalByRefObject, ITestEventListener -#else - public class TestEventHandler : ITestEventListener -#endif - { - private readonly ExtendedTextWriter _outWriter; - - private readonly bool _displayBeforeTest; - private readonly bool _displayAfterTest; - private readonly bool _displayBeforeOutput; - - private string? _lastTestOutput; - private bool _wantNewLine = false; - - public TestEventHandler(ExtendedTextWriter outWriter, string labelsOption) - { - _outWriter = outWriter; - - labelsOption = labelsOption.ToUpperInvariant(); - _displayBeforeTest = labelsOption == "BEFORE" || labelsOption == "BEFOREANDAFTER"; - _displayAfterTest = labelsOption == "AFTER" || labelsOption == "BEFOREANDAFTER"; - _displayBeforeOutput = _displayBeforeTest || _displayAfterTest || labelsOption == "ONOUTPUTONLY"; - } - - public void OnTestEvent(string report) - { - var doc = new XmlDocument(); - doc.LoadXml(report); - - var testEvent = doc.FirstChild; - if (testEvent == null) - return; - - switch (testEvent.Name) - { - case "start-test": - TestStarted(testEvent); - break; - - case "test-case": - TestFinished(testEvent); - break; - - case "test-suite": - SuiteFinished(testEvent); - break; - - case "test-output": - TestOutput(testEvent); - break; - } - } - - private void TestStarted(XmlNode testResult) - { - var testName = testResult.Attributes?["fullname"]?.Value; - - if (_displayBeforeTest && testName != null) - WriteLabelLine(testName); - } - - private void TestFinished(XmlNode testResult) - { - var testName = testResult.Attributes?["fullname"]?.Value; - - if (testName == null) - return; - - var status = testResult.GetAttribute("label") ?? testResult.GetAttribute("result") ?? "Unknown"; - var outputNode = testResult.SelectSingleNode("output"); - - if (outputNode != null) - { - if (_displayBeforeOutput) - WriteLabelLine(testName); - - FlushNewLineIfNeeded(); - WriteOutputLine(testName, outputNode.InnerText); - } - - if (_displayAfterTest) - WriteLabelLineAfterTest(testName, status); - } - - private void SuiteFinished(XmlNode testResult) - { - var suiteName = testResult.Attributes?["fullname"]?.Value; - var outputNode = testResult.SelectSingleNode("output"); - - if (suiteName != null && outputNode != null) - { - if (_displayBeforeOutput) - WriteLabelLine(suiteName); - - FlushNewLineIfNeeded(); - WriteOutputLine(suiteName, outputNode.InnerText); - } - } - - private void TestOutput(XmlNode outputNode) - { - var testName = outputNode.GetAttribute("testname"); - - if (testName != null) - { - if (_displayBeforeOutput) - WriteLabelLine(testName); - - var stream = outputNode.GetAttribute("stream"); - WriteOutputLine(testName, outputNode.InnerText, stream == "Error" ? ColorStyle.Error : ColorStyle.Output); - } - } - - private string? _currentLabel; - - private void WriteLabelLine(string label) - { - if (label != _currentLabel) - { - FlushNewLineIfNeeded(); - _lastTestOutput = label; - - _outWriter.WriteLine(ColorStyle.SectionHeader, $"=> {label}"); - - _currentLabel = label; - } - } - - private void WriteLabelLineAfterTest(string label, string status) - { - FlushNewLineIfNeeded(); - _lastTestOutput = label; - - if (status != null) - { - _outWriter.Write(GetColorForResultStatus(status), $"{status} "); - } - - _outWriter.WriteLine(ColorStyle.SectionHeader, $"=> {label}"); - - _currentLabel = label; - } - - private void WriteOutputLine(string testName, string text) - { - WriteOutputLine(testName, text, ColorStyle.Output); - } - - private void WriteOutputLine(string testName, string text, ColorStyle color) - { - if (_lastTestOutput != testName) - { - FlushNewLineIfNeeded(); - _lastTestOutput = testName; - } - - _outWriter.Write(color, text); - - // If the text we just wrote did not have a new line, flag that we should eventually emit one. - if (!text.EndsWith("\n")) - { - _wantNewLine = true; - } - } - - private void FlushNewLineIfNeeded() - { - if (_wantNewLine) - { - _outWriter.WriteLine(); - _wantNewLine = false; - } - } - - private static ColorStyle GetColorForResultStatus(string status) - { - switch (status) - { - case "Passed": - return ColorStyle.Pass; - case "Failed": - return ColorStyle.Failure; - case "Error": - case "Invalid": - case "Cancelled": - return ColorStyle.Error; - case "Warning": - case "Ignored": - return ColorStyle.Warning; - default: - return ColorStyle.Output; - } - } - -#if NETFRAMEWORK - public override object InitializeLifetimeService() - { - return null!; - } -#endif - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/Utilities/SaveConsoleOutput.cs b/src/NUnitConsole/nunit4-netcore-console/Utilities/SaveConsoleOutput.cs deleted file mode 100644 index a5fa46d08..000000000 --- a/src/NUnitConsole/nunit4-netcore-console/Utilities/SaveConsoleOutput.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using System; -using System.IO; - -namespace NUnit.ConsoleRunner.Utilities -{ - /// - /// Saves Console.Out and Console.Error and restores them when the object - /// is destroyed - /// - public sealed class SaveConsoleOutput : IDisposable - { - private readonly TextWriter _savedOut = Console.Out; - private readonly TextWriter _savedError = Console.Error; - - /// - /// Restores Console.Out and Console.Error - /// - public void Dispose() - { - Console.SetOut(_savedOut); - Console.SetError(_savedError); - } - } -} diff --git a/src/NUnitConsole/nunit4-netcore-console/nunit4-netcore-console.csproj b/src/NUnitConsole/nunit4-netcore-console/nunit4-netcore-console.csproj index d0d29ef09..369e5795a 100644 --- a/src/NUnitConsole/nunit4-netcore-console/nunit4-netcore-console.csproj +++ b/src/NUnitConsole/nunit4-netcore-console/nunit4-netcore-console.csproj @@ -46,6 +46,27 @@ + + + + + + + + + + + + + + + + + + + + +