From 6251bd7066612d4a1bf7a28845acb7e3dbee9b0c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 3 Jan 2025 11:56:19 -0800 Subject: [PATCH 01/16] 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 @@ + + + + + + + + + + + + + + + + + + + + + From ad9b87ecce4dfc2ad020a14d48bdc8d53a6717d1 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Fri, 3 Jan 2025 15:35:43 -0800 Subject: [PATCH 02/16] Eliminate NUnitNetStandardDriver; clean up unused conditionals --- .../Extensibility/IDriverFactory.cs | 8 +- .../Drivers/NUnitNetStandardDriverTests.cs | 125 ------------ .../Services/DriverServiceTests.cs | 8 +- .../Drivers/NUnit3DriverFactory.cs | 5 - .../Drivers/NUnitNetStandardDriver.cs | 187 ------------------ .../Internal/DomainDetailsBuilder.cs | 2 - .../nunit.engine.core/Internal/PathUtils.cs | 2 +- .../Runners/MasterTestRunnerTests.cs | 2 +- .../Services/TestFilteringTests.cs | 4 - .../Remoting/TestAgentRemotingProxy.cs | 2 - .../nunit.engine/Services/AgentStatus.cs | 2 - .../Services/AgentStore.AgentRecord.cs | 2 - .../nunit.engine/Services/AgentStore.cs | 2 - .../nunit.engine/Services/ExtensionService.cs | 10 - 14 files changed, 7 insertions(+), 354 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs delete mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs diff --git a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs index c8eb3c7e3..fda047506 100644 --- a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs @@ -19,23 +19,23 @@ public interface IDriverFactory /// An AssemblyName referring to the possible test framework. bool IsSupportedTestFramework(AssemblyName reference); -#if NETSTANDARD || NETCOREAPP +#if NETFRAMEWORK /// /// Gets a driver for a given test assembly and a framework /// which the assembly is already known to reference. /// + /// The domain in which the assembly will be loaded /// An AssemblyName referring to the test framework. /// - IFrameworkDriver GetDriver(AssemblyName reference); + IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference); #else /// /// Gets a driver for a given test assembly and a framework /// which the assembly is already known to reference. /// - /// The domain in which the assembly will be loaded /// An AssemblyName referring to the test framework. /// - IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference); + IFrameworkDriver GetDriver(AssemblyName reference); #endif } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs deleted file mode 100644 index a41af10b3..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitNetStandardDriverTests.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETCOREAPP2_1 - -using System; -using System.Collections.Generic; -using System.Xml; -using NUnit.TestData.Assemblies; -using NUnit.Framework; -using NUnit.Engine.Extensibility; -using NUnit.Engine.Internal; - -namespace NUnit.Engine.Drivers.Tests -{ - [TestOf(typeof(NUnitNetStandardDriver))] - public class NUnitNetStandardDriverTests - { - private const string MOCK_ASSEMBLY = "mock-assembly.dll"; - private const string MISSING_FILE = "junk.dll"; - private const string NUNIT_FRAMEWORK = "nunit.framework"; - private const string LOAD_MESSAGE = "Method called without calling Load first"; - - private IDictionary _settings = new Dictionary(); - - private IFrameworkDriver _driver; - private string _mockAssemblyPath; - - [SetUp] - public void CreateDriver() - { - _mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); - _driver = new NUnitNetStandardDriver(); - - } - - [Test] - public void Load_GoodFile_ReturnsRunnableSuite() - { - var result = XmlHelper.CreateXmlNode(_driver.Load(_mockAssemblyPath, _settings)); - - Assert.That(result.Name, Is.EqualTo("test-suite")); - Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); - Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); - Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); - Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); - } - - [Test] - public void Explore_AfterLoad_ReturnsRunnableSuite() - { - _driver.Load(_mockAssemblyPath, _settings); - var result = XmlHelper.CreateXmlNode(_driver.Explore(TestFilter.Empty.Text)); - - Assert.That(result.Name, Is.EqualTo("test-suite")); - Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); - Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); - Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); - Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); - } - - [Test] - public void ExploreTestsAction_WithoutLoad_ThrowsInvalidOperationException() - { - var ex = Assert.Catch(() => _driver.Explore(TestFilter.Empty.Text)); - if (ex is System.Reflection.TargetInvocationException) - ex = ex.InnerException; - Assert.That(ex, Is.TypeOf()); - Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); - } - - [Test] - public void CountTestsAction_AfterLoad_ReturnsCorrectCount() - { - _driver.Load(_mockAssemblyPath, _settings); - Assert.That(_driver.CountTestCases(TestFilter.Empty.Text), Is.EqualTo(MockAssembly.Tests)); - } - - [Test] - public void CountTestsAction_WithoutLoad_ThrowsInvalidOperationException() - { - var ex = Assert.Catch(() => _driver.CountTestCases(TestFilter.Empty.Text)); - if (ex is System.Reflection.TargetInvocationException) - ex = ex.InnerException; - Assert.That(ex, Is.TypeOf()); - Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); - } - - [Test] - public void RunTestsAction_AfterLoad_ReturnsRunnableSuite() - { - _driver.Load(_mockAssemblyPath, _settings); - var result = XmlHelper.CreateXmlNode(_driver.Run(new NullListener(), TestFilter.Empty.Text)); - - Assert.That(result.Name, Is.EqualTo("test-suite")); - Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); - Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); - Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); - Assert.That(result.GetAttribute("result"), Is.EqualTo("Failed")); - Assert.That(result.GetAttribute("passed"), Is.EqualTo(MockAssembly.PassedInAttribute.ToString())); - Assert.That(result.GetAttribute("failed"), Is.EqualTo(MockAssembly.Failed.ToString())); - Assert.That(result.GetAttribute("skipped"), Is.EqualTo(MockAssembly.Skipped.ToString())); - Assert.That(result.GetAttribute("inconclusive"), Is.EqualTo(MockAssembly.Inconclusive.ToString())); - Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); - } - - [Test] - public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() - { - var ex = Assert.Catch(() => _driver.Run(new NullListener(), TestFilter.Empty.Text)); - if (ex is System.Reflection.TargetInvocationException) - ex = ex.InnerException; - Assert.That(ex, Is.TypeOf()); - Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); - } - - public class NullListener : ITestEventListener - { - public void OnTestEvent(string testEvent) - { - // No action - } - } - } -} -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index 61122628c..602c06242 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -38,16 +38,10 @@ public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Typ new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), //new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), -#elif NETCOREAPP3_1 +#else new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), //new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), -// TODO: This is never used. We need to test net standard driver in some way, possibly -// by forcing it's use in a separate test. -#elif NETCOREAPP2_1 - new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetStandardDriver)), - new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetStandardDriver)), - new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetStandardDriver)), #endif // Invalid cases should work with all target runtimes new TestCaseData("mock-assembly.pdb", false, typeof(InvalidAssemblyFrameworkDriver)), diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index 5186570b0..78bf43c6f 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -47,13 +47,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) public IFrameworkDriver GetDriver(AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); -#if NETSTANDARD - log.Info("Using NUnitNetStandardDriver"); - return new NUnitNetStandardDriver(); -#else log.Info("Using NUnitNetCore31Driver"); return new NUnitNetCore31Driver(); -#endif } #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs deleted file mode 100644 index 275ac2aef..000000000 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetStandardDriver.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETSTANDARD -using System; -using System.Linq; -using System.Collections.Generic; -using NUnit.Engine.Internal; -using System.Reflection; -using NUnit.Engine.Extensibility; -using Mono.Cecil; - -namespace NUnit.Engine.Drivers -{ - /// - /// NUnitNetStandardDriver is used by the test-runner to load and run - /// tests using the NUnit framework assembly. - /// - /// NUnitNetStandardDriver was the original driver for the .NET Standard builds - /// of the engine, however has an issue with loading .NET Core assemblies - /// (https://github.com/nunit/nunit-console/issues/710) - /// is the replacement driver for running .NET Core tests, - /// and should be preferred for use with .NET Core 3.1 and later. - /// - public class NUnitNetStandardDriver : IFrameworkDriver - { - const string LOAD_MESSAGE = "Method called without calling Load first"; - const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; - const string FAILED_TO_LOAD_TEST_ASSEMBLY = "Failed to load the test assembly {0}"; - const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; - - static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - static readonly string LOAD_METHOD = "LoadTests"; - static readonly string EXPLORE_METHOD = "ExploreTests"; - static readonly string COUNT_METHOD = "CountTests"; - static readonly string RUN_METHOD = "RunTests"; - static readonly string RUN_ASYNC_METHOD = "RunTests"; - static readonly string STOP_RUN_METHOD = "StopRun"; - - static ILogger log = InternalTrace.GetLogger(nameof(NUnitNetStandardDriver)); - - Assembly _testAssembly; - Assembly _frameworkAssembly; - object _frameworkController; - Type _frameworkControllerType; - - /// - /// An id prefix that will be passed to the test framework and used as part of the - /// test ids created. - /// - public string ID { get; set; } - - /// - /// Loads the tests in an assembly. - /// - /// The NUnit Framework that the tests reference - /// The test assembly - /// The test settings - /// An Xml string representing the loaded test - public string Load(string testAssembly, IDictionary settings) - { - var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - - using (var assemblyRef = AssemblyDefinition.ReadAssembly(testAssembly)) { - _testAssembly = Assembly.Load(new AssemblyName(assemblyRef.FullName)); - if(_testAssembly == null) - throw new NUnitEngineException(string.Format(FAILED_TO_LOAD_TEST_ASSEMBLY, assemblyRef.FullName)); - - var nunitRef = assemblyRef.MainModule.AssemblyReferences.Where(reference => reference.Name.Equals("nunit.framework", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); - if (nunitRef == null) - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT); - - var nunit = Assembly.Load(new AssemblyName(nunitRef.FullName)); - if (nunit == null) - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT); - - _frameworkAssembly = nunit; - - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); - if (_frameworkController == null) - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - - _frameworkControllerType = _frameworkController.GetType(); - - log.Info("Loading {0} - see separate log file", _testAssembly.FullName); - return ExecuteMethod(LOAD_METHOD) as string; - } - } - - /// - /// Counts the number of test cases for the loaded test assembly - /// - /// The XML test filter - /// The number of test cases - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - object count = ExecuteMethod(COUNT_METHOD, filter); - return count != null ? (int)count : 0; - } - - /// - /// Executes the tests in an assembly. - /// - /// An ITestEventHandler that receives progress notices - /// A filter that controls which tests are executed - /// An Xml string representing the result - public string Run(ITestEventListener listener, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.FullName); - Action callback = listener != null ? listener.OnTestEvent : (Action)null; - return ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter) as string; - } - - /// - /// Executes the tests in an assembly asyncronously. - /// - /// A callback that receives XML progress notices - /// A filter that controls which tests are executed - public void RunAsync(Action callback, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.FullName); - ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } - - /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. - /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public void StopRun(bool force) - { - ExecuteMethod(STOP_RUN_METHOD, force); - } - - /// - /// Returns information about the tests in an assembly. - /// - /// A filter indicating which tests to include - /// An Xml string representing the tests - public string Explore(string filter) - { - CheckLoadWasCalled(); - - log.Info("Exploring {0} - see separate log file", _testAssembly.FullName); - return ExecuteMethod(EXPLORE_METHOD, filter) as string; - } - - void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } - - object CreateObject(string typeName, params object[] args) - { - var typeinfo = _frameworkAssembly.DefinedTypes.FirstOrDefault(t => t.FullName == typeName); - if (typeinfo == null) - { - log.Error("Could not find type {0}", typeName); - } - return Activator.CreateInstance(typeinfo.AsType(), args); - } - - object ExecuteMethod(string methodName, params object[] args) - { - var method = _frameworkControllerType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); - return ExecuteMethod(method, args); - } - - object ExecuteMethod(string methodName, Type[] ptypes, params object[] args) - { - var method = _frameworkControllerType.GetMethod(methodName, ptypes); - return ExecuteMethod(method, args); - } - - object ExecuteMethod(MethodInfo method, params object[] args) - { - if (method == null) - { - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } - return method.Invoke(_frameworkController, args); - } - } -} -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core/Internal/DomainDetailsBuilder.cs b/src/NUnitEngine/nunit.engine.core/Internal/DomainDetailsBuilder.cs index a3e3d6691..89827df5f 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/DomainDetailsBuilder.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/DomainDetailsBuilder.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if !NETSTANDARD2_0 using System; using System.Collections.Generic; using System.Reflection; @@ -63,4 +62,3 @@ private static void WriteAssemblyInformation(StringBuilder sb, Assembly assembly } } } -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs index 659388949..205f12ab0 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/PathUtils.cs @@ -226,7 +226,7 @@ public static bool IsValidPath(string path) try { var info = GetFileSystemInfo(path); -#if NETCOREAPP2_1_OR_GREATER +#if NETCOREAPP var creation = info.CreationTime; #endif return true; // Whether it exists or not! diff --git a/src/NUnitEngine/nunit.engine.tests/Runners/MasterTestRunnerTests.cs b/src/NUnitEngine/nunit.engine.tests/Runners/MasterTestRunnerTests.cs index 69037b533..e4e924857 100644 --- a/src/NUnitEngine/nunit.engine.tests/Runners/MasterTestRunnerTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Runners/MasterTestRunnerTests.cs @@ -65,7 +65,7 @@ public MasterTestRunnerTests(TestRunData testRunData) // 1. These tests document current behavior. In some cases we may want to change that behavior. // 2. The .NET Standard build does not seem to handle notest-assembly correctly, so those entries are commented out. // 3. The .NET Standard build is not intended to handle projects. -#if NETCOREAPP2_1_OR_GREATER +#if NETCOREAPP new TestRunData( MOCK_ASSEMBLY, MockAssemblyData ), new TestRunData( $"{MOCK_ASSEMBLY},{MOCK_ASSEMBLY}", MockAssemblyData, MockAssemblyData ), //new TestRunData( "notest-assembly.dll", NoTestAssemblyData ), diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs index 528e7a051..71ff0a44b 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs @@ -16,8 +16,6 @@ public class TestFilteringTests #if NETCOREAPP3_1_OR_GREATER private NUnitNetCore31Driver _driver; -#elif NETCOREAPP2_1 - private NUnitNetStandardDriver _driver; #else private NUnit3FrameworkDriver _driver; #endif @@ -28,8 +26,6 @@ public void LoadAssembly() var mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); #if NETCOREAPP3_1_OR_GREATER _driver = new NUnitNetCore31Driver(); -#elif NETCOREAPP2_1 - _driver = new NUnitNetStandardDriver(); #else var assemblyName = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); _driver = new NUnit3FrameworkDriver(AppDomain.CurrentDomain, assemblyName); diff --git a/src/NUnitEngine/nunit.engine/Communication/Transports/Remoting/TestAgentRemotingProxy.cs b/src/NUnitEngine/nunit.engine/Communication/Transports/Remoting/TestAgentRemotingProxy.cs index fa17a0745..15793a6de 100644 --- a/src/NUnitEngine/nunit.engine/Communication/Transports/Remoting/TestAgentRemotingProxy.cs +++ b/src/NUnitEngine/nunit.engine/Communication/Transports/Remoting/TestAgentRemotingProxy.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if !NETSTANDARD2_0 using System; using NUnit.Engine; @@ -39,4 +38,3 @@ public void Stop() } } } -#endif diff --git a/src/NUnitEngine/nunit.engine/Services/AgentStatus.cs b/src/NUnitEngine/nunit.engine/Services/AgentStatus.cs index 8a600d641..67911a74d 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentStatus.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentStatus.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if !NETSTANDARD2_0 namespace NUnit.Engine.Services { /// @@ -28,4 +27,3 @@ public enum AgentStatus Terminated } } -#endif diff --git a/src/NUnitEngine/nunit.engine/Services/AgentStore.AgentRecord.cs b/src/NUnitEngine/nunit.engine/Services/AgentStore.AgentRecord.cs index 27246d80f..849b6cf24 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentStore.AgentRecord.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentStore.AgentRecord.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if !NETSTANDARD2_0 using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -49,4 +48,3 @@ public AgentRecord Terminated() } } } -#endif diff --git a/src/NUnitEngine/nunit.engine/Services/AgentStore.cs b/src/NUnitEngine/nunit.engine/Services/AgentStore.cs index 6c7bd51f0..395992fa7 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentStore.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentStore.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if !NETSTANDARD2_0 using System; using System.Collections.Generic; using System.Diagnostics; @@ -87,4 +86,3 @@ public void MarkTerminated(Guid agentId) } } } -#endif diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index 330fa9dad..f994c09b9 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -5,17 +5,7 @@ using System.Reflection; using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; -using NUnit.Engine.Internal.Backports; using NUnit.Engine.Internal.FileSystemAccess; -using NUnit.Engine.Internal.FileSystemAccess.Default; -using TestCentric.Metadata; - -using Backports = NUnit.Engine.Internal.Backports; -#if NET20 || NETSTANDARD2_0 -using Path = NUnit.Engine.Internal.Backports.Path; -#else -using Path = System.IO.Path; -#endif namespace NUnit.Engine.Services { From d82d2717042e98610c10b8239964235236a76e08 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 4 Jan 2025 10:47:29 -0800 Subject: [PATCH 03/16] Refactor existing drivers to bring the code closer together --- .../Drivers/NUnit3DriverFactory.cs | 10 +- .../Drivers/NUnit3FrameworkDriver.cs | 55 +++---- .../Drivers/NUnitNetCore31Driver.cs | 135 +++++++++++------- .../Services/TestFilteringTests.cs | 4 +- 4 files changed, 120 insertions(+), 84 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index 78bf43c6f..8142942b3 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -25,12 +25,11 @@ public bool IsSupportedTestFramework(AssemblyName reference) #if NETFRAMEWORK /// - /// Gets a driver for a given test assembly and a framework - /// which the assembly is already known to reference. + /// Gets a driver for a given test framework. /// /// The domain in which the assembly will be loaded /// An AssemblyName referring to the test framework. - /// + /// An IFrameworkDriver public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); @@ -39,8 +38,7 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) } #else /// - /// Gets a driver for a given test assembly and a framework - /// which the assembly is already known to reference. + /// Gets a driver for a given test framework. /// /// An AssemblyName referring to the test framework. /// @@ -48,7 +46,7 @@ public IFrameworkDriver GetDriver(AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); log.Info("Using NUnitNetCore31Driver"); - return new NUnitNetCore31Driver(); + return new NUnitNetCore31Driver(reference); } #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs index 8084d79b0..285863788 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs @@ -9,7 +9,6 @@ using NUnit.Common; using NUnit.Engine.Internal; using NUnit.Engine.Extensibility; -using System.Diagnostics.CodeAnalysis; namespace NUnit.Engine.Drivers { @@ -19,8 +18,10 @@ namespace NUnit.Engine.Drivers /// public class NUnit3FrameworkDriver : IFrameworkDriver { + // Messages private const string LOAD_MESSAGE = "Method called without calling Load first"; + // API Constants private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; private static readonly string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; private static readonly string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; @@ -28,42 +29,49 @@ public class NUnit3FrameworkDriver : IFrameworkDriver private static readonly string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; private static readonly string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; - static readonly ILogger log = InternalTrace.GetLogger("NUnitFrameworkDriver"); + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnit3FrameworkDriver)); readonly AppDomain _testDomain; - readonly AssemblyName _reference; + readonly AssemblyName _nunitRef; string? _testAssemblyPath; object? _frameworkController; + Type? _frameworkControllerType; /// /// Construct an NUnit3FrameworkDriver /// /// The application domain in which to create the FrameworkController - /// An AssemblyName referring to the test framework. - public NUnit3FrameworkDriver(AppDomain testDomain, AssemblyName reference) + /// An AssemblyName referring to the test framework. + public NUnit3FrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) { _testDomain = testDomain; - _reference = reference; + _nunitRef = nunitRef; } + /// + /// An id prefix that will be passed to the test framework and used as part of the + /// test ids created. + /// public string ID { get; set; } = string.Empty; /// /// Loads the tests in an assembly. /// - /// An Xml string representing the loaded test + /// The path to the test assembly + /// The test settings + /// An XML string representing the loaded test public string Load(string testAssemblyPath, IDictionary settings) { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver constructor called with a file name that doesn't exist.", "testAssemblyPath"); - + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + log.Debug($"Loading {testAssemblyPath}"); var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - // Normally, the runner should check for an invalid requested runtime, but we make sure here + // Normally, the caller should check for an invalid requested runtime, but we make sure here var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - _testAssemblyPath = testAssemblyPath; + _testAssemblyPath = Path.GetFullPath(testAssemblyPath); try { @@ -78,6 +86,9 @@ public string Load(string testAssemblyPath, IDictionary settings throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); } + _frameworkControllerType = _frameworkController.GetType(); + log.Debug($"Created FrameworkControler {_frameworkControllerType.Name}"); + CallbackHandler handler = new CallbackHandler(); var fileName = Path.GetFileName(_testAssemblyPath); @@ -86,19 +97,21 @@ public string Load(string testAssemblyPath, IDictionary settings CreateObject(LOAD_ACTION, _frameworkController, handler); - log.Info("Loaded {0}", fileName); + log.Debug($"Loaded {testAssemblyPath}"); return handler.Result.ShouldNotBeNull(); } + /// + /// Counts the number of test cases for the loaded test assembly + /// + /// The XML test filter + /// The number of test cases public int CountTestCases(string filter) { CheckLoadWasCalled(); - CallbackHandler handler = new CallbackHandler(); - CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return int.Parse(handler.Result.ShouldNotBeNull()); } @@ -111,12 +124,9 @@ public int CountTestCases(string filter) public string Run(ITestEventListener? listener, string filter) { CheckLoadWasCalled(); - - var handler = new RunTestsCallbackHandler(listener); - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + var handler = new RunTestsCallbackHandler(listener); CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); } @@ -137,12 +147,9 @@ public void StopRun(bool force) public string Explore(string filter) { CheckLoadWasCalled(); - - CallbackHandler handler = new CallbackHandler(); - log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + CallbackHandler handler = new CallbackHandler(); CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); } @@ -157,7 +164,7 @@ private object CreateObject(string typeName, params object?[]? args) try { return _testDomain.CreateInstanceAndUnwrap( - _reference.FullName, typeName, false, 0, null, args, null, null )!; + _nunitRef.FullName, typeName, false, 0, null, args, null, null )!; } catch (TargetInvocationException ex) { diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs index 7199f9a8f..a0712cead 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs @@ -1,15 +1,14 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if NETCOREAPP3_1_OR_GREATER +#if NETCOREAPP using System; using System.Linq; using System.Collections.Generic; using System.IO; -using NUnit.Engine.Internal; using System.Reflection; -using NUnit.Engine.Extensibility; -using System.Diagnostics; using NUnit.Common; +using NUnit.Engine.Internal; +using NUnit.Engine.Extensibility; namespace NUnit.Engine.Drivers { @@ -21,27 +20,40 @@ namespace NUnit.Engine.Drivers /// public class NUnitNetCore31Driver : IFrameworkDriver { - const string LOAD_MESSAGE = "Method called without calling Load first"; + private const string LOAD_MESSAGE = "Method called without calling Load first"; const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; - const string FAILED_TO_LOAD_TEST_ASSEMBLY = "Failed to load the test assembly {0}"; + const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; - static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - static readonly string LOAD_METHOD = "LoadTests"; - static readonly string EXPLORE_METHOD = "ExploreTests"; - static readonly string COUNT_METHOD = "CountTests"; - static readonly string RUN_METHOD = "RunTests"; - static readonly string RUN_ASYNC_METHOD = "RunTests"; - static readonly string STOP_RUN_METHOD = "StopRun"; + private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; + private static readonly string LOAD_METHOD = "LoadTests"; + private static readonly string EXPLORE_METHOD = "ExploreTests"; + private static readonly string COUNT_METHOD = "CountTests"; + private static readonly string RUN_METHOD = "RunTests"; + private static readonly string RUN_ASYNC_METHOD = "RunTests"; + private static readonly string STOP_RUN_METHOD = "StopRun"; - static ILogger log = InternalTrace.GetLogger(nameof(NUnitNetCore31Driver)); + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitNetCore31Driver)); + + readonly AssemblyName _nunitRef; + string? _testAssemblyPath; - Assembly? _testAssembly; - Assembly? _frameworkAssembly; object? _frameworkController; Type? _frameworkControllerType; + Assembly? _testAssembly; + Assembly? _frameworkAssembly; TestAssemblyLoadContext? _assemblyLoadContext; + /// + /// Construct an NUnitNetCore31Driver + /// + /// An AssemblyName referring to the test framework. + public NUnitNetCore31Driver(AssemblyName nunitRef) + { + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + _nunitRef = nunitRef; + } + /// /// An id prefix that will be passed to the test framework and used as part of the /// test ids created. @@ -51,46 +63,24 @@ public class NUnitNetCore31Driver : IFrameworkDriver /// /// Loads the tests in an assembly. /// - /// The path to the test assembly + /// The path to the test assembly /// The test settings /// An XML string representing the loaded test - public string Load(string assemblyPath, IDictionary settings) + public string Load(string testAssemblyPath, IDictionary settings) { - log.Debug($"Loading {assemblyPath}"); + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + log.Debug($"Loading {testAssemblyPath}"); var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - assemblyPath = Path.GetFullPath(assemblyPath); //AssemblyLoadContext requires an absolute path - _assemblyLoadContext = new TestAssemblyLoadContext(assemblyPath); - - try - { - _testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); - } - catch (Exception e) - { - var msg = string.Format(FAILED_TO_LOAD_TEST_ASSEMBLY, assemblyPath); - log.Error(msg); - throw new NUnitEngineException(msg, e); - } - log.Debug($"Loaded {assemblyPath}"); + // Normally, the caller should check for an invalid requested runtime, but we make sure here + var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) + ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - var nunitRef = _testAssembly.GetReferencedAssemblies().FirstOrDefault(reference => string.Equals(reference.Name, "nunit.framework", StringComparison.OrdinalIgnoreCase)); - if (nunitRef == null) - { - log.Error(FAILED_TO_LOAD_NUNIT); - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT); - } + _testAssemblyPath = Path.GetFullPath(testAssemblyPath); + _assemblyLoadContext = new TestAssemblyLoadContext(_testAssemblyPath); - try - { - _frameworkAssembly = _assemblyLoadContext.LoadFromAssemblyName(nunitRef); - } - catch (Exception e) - { - log.Error($"{FAILED_TO_LOAD_NUNIT}\r\n{e}"); - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); - } - log.Debug("Loaded nunit.framework"); + _testAssembly = LoadAssembly(_testAssemblyPath!); + _frameworkAssembly = LoadAssembly(_nunitRef); _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); if (_frameworkController == null) @@ -161,23 +151,64 @@ public void StopRun(bool force) public string Explore(string filter) { CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); return (string)ExecuteMethod(EXPLORE_METHOD, filter); } - void CheckLoadWasCalled() + private void CheckLoadWasCalled() { if (_frameworkController == null) throw new InvalidOperationException(LOAD_MESSAGE); } - object CreateObject(string typeName, params object?[]? args) + private object CreateObject(string typeName, params object?[]? args) { var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; return Activator.CreateInstance(type, args)!; } + private Assembly LoadAssembly(string assemblyPath) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyPath returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); + log.Error(msg); + throw new NUnitEngineException(msg, e); + } + + log.Debug($"Loaded {assemblyPath}"); + return assembly; + } + + private Assembly LoadAssembly(AssemblyName assemblyName) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyName returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); + log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); + throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); + } + + log.Debug($"Loaded {assemblyName.FullName}"); + return assembly; + } + object ExecuteMethod(string methodName, params object?[] args) { var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs index 71ff0a44b..9c16b00bf 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs @@ -24,10 +24,10 @@ public class TestFilteringTests public void LoadAssembly() { var mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); + var assemblyName = typeof(TestAttribute).Assembly.GetName(); #if NETCOREAPP3_1_OR_GREATER - _driver = new NUnitNetCore31Driver(); + _driver = new NUnitNetCore31Driver(assemblyName); #else - var assemblyName = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); _driver = new NUnit3FrameworkDriver(AppDomain.CurrentDomain, assemblyName); #endif _driver.Load(mockAssemblyPath, new Dictionary()); From 42e60116930a6824df2d13c2825f6952905ecd27 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 4 Jan 2025 16:41:19 -0800 Subject: [PATCH 04/16] Create new combined driver --- .../Services/DriverServiceTests.cs | 23 +- .../Drivers/NUnit3CombinedFrameworkDriver.cs | 337 ++++++++++++++++++ .../Drivers/NUnit3DriverFactory.cs | 8 +- 3 files changed, 347 insertions(+), 21 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index 602c06242..204b45560 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -30,29 +30,18 @@ public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Typ static TestCaseData[] DriverSelectionTestCases = new[] { // TODO: make commented tests work -#if NETFRAMEWORK - new TestCaseData("mock-assembly.dll", false, typeof(NUnit3FrameworkDriver)), - new TestCaseData("mock-assembly.dll", true, typeof(NUnit3FrameworkDriver)), - //new TestCaseData("notest-assembly.dll", false, typeof(NUnit3FrameworkDriver)), -#elif NET5_0_OR_GREATER - new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), - new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), - //new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), -#else - new TestCaseData("mock-assembly.dll", false, typeof(NUnitNetCore31Driver)), - new TestCaseData("mock-assembly.dll", true, typeof(NUnitNetCore31Driver)), - //new TestCaseData("notest-assembly.dll", false, typeof(NUnitNetCore31Driver)), -#endif -// Invalid cases should work with all target runtimes + new TestCaseData("mock-assembly.dll", false, typeof(NUnit3CombinedFrameworkDriver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnit3CombinedFrameworkDriver)), + //new TestCaseData("notest-assembly.dll", false, typeof(NUnit3CombinedFrameworkDriver)), + //new TestCaseData"notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)) + + // Invalid cases should work with all target runtimes new TestCaseData("mock-assembly.pdb", false, typeof(InvalidAssemblyFrameworkDriver)), new TestCaseData("mock-assembly.pdb", true, typeof(InvalidAssemblyFrameworkDriver)), new TestCaseData("junk.dll", false, typeof(InvalidAssemblyFrameworkDriver)), new TestCaseData("junk.dll", true, typeof(InvalidAssemblyFrameworkDriver)), new TestCaseData("nunit.engine.core.dll", false, typeof(InvalidAssemblyFrameworkDriver)), new TestCaseData("nunit.engine.core.dll", true, typeof(SkippedAssemblyFrameworkDriver)) -//#if !NET5_0_OR_GREATER // Not yet working -// new TestCaseData"notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)) -//#endif }; [Test] diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs new file mode 100644 index 000000000..ecd069443 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs @@ -0,0 +1,337 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using NUnit.Common; +using NUnit.Engine.Internal; +using NUnit.Engine.Extensibility; + +namespace NUnit.Engine.Drivers +{ + /// + /// NUnit3CombinedFrameworkDriver is used by the test-runner to load and run + /// tests using the NUnit framework assembly. + /// + public class NUnit3CombinedFrameworkDriver : IFrameworkDriver + { + private const string LOAD_MESSAGE = "Method called without calling Load first"; + const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; + const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; + const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; + + private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; +#if NETFRAMEWORK + private static readonly string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; + private static readonly string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; + private static readonly string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; + private static readonly string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; + private static readonly string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; +#else + private static readonly string LOAD_METHOD = "LoadTests"; + private static readonly string EXPLORE_METHOD = "ExploreTests"; + private static readonly string COUNT_METHOD = "CountTests"; + private static readonly string RUN_METHOD = "RunTests"; + private static readonly string RUN_ASYNC_METHOD = "RunTests"; + private static readonly string STOP_RUN_METHOD = "StopRun"; +#endif + + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnit3CombinedFrameworkDriver)); + + readonly AssemblyName _nunitRef; + string? _testAssemblyPath; + + object? _frameworkController; + Type? _frameworkControllerType; + +#if NETFRAMEWORK + readonly AppDomain _testDomain; +#else + Assembly? _testAssembly; + Assembly? _frameworkAssembly; + TestAssemblyLoadContext? _assemblyLoadContext; +#endif + +#if NETFRAMEWORK + /// + /// Construct an NUnit3CombinedFrameworkDriver + /// + /// The application domain in which to create the FrameworkController + /// An AssemblyName referring to the test framework. + public NUnit3CombinedFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) + { + _testDomain = testDomain; + _nunitRef = nunitRef; + } +#else + /// + /// Construct an NUnitNetCore31Driver + /// + /// An AssemblyName referring to the test framework. + public NUnit3CombinedFrameworkDriver(AssemblyName nunitRef) + { + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + _nunitRef = nunitRef; + } +#endif + + /// + /// An id prefix that will be passed to the test framework and used as part of the + /// test ids created. + /// + public string ID { get; set; } = string.Empty; + + /// + /// Loads the tests in an assembly. + /// + /// The path to the test assembly + /// The test settings + /// An XML string representing the loaded test + public string Load(string testAssemblyPath, IDictionary settings) + { + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + log.Debug($"Loading {testAssemblyPath}"); + var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; + + // Normally, the caller should check for an invalid requested runtime, but we make sure here + var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) + ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; + + _testAssemblyPath = Path.GetFullPath(testAssemblyPath); +#if NETFRAMEWORK + try + { + _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); + } + catch (BadImageFormatException ex) when (requestedRuntime != null) + { + throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); + } + catch (SerializationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); + } +#else + _assemblyLoadContext = new TestAssemblyLoadContext(_testAssemblyPath); + + _testAssembly = LoadAssembly(_testAssemblyPath!); + _frameworkAssembly = LoadAssembly(_nunitRef); + + _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); + if (_frameworkController == null) + { + log.Error(INVALID_FRAMEWORK_MESSAGE); + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + } +#endif + + _frameworkControllerType = _frameworkController.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); + + var fileName = Path.GetFileName(_testAssemblyPath); + log.Info("Loading {0} - see separate log file", fileName); + +#if NETFRAMEWORK + return ExecuteAction(LOAD_ACTION); + //log.Debug($"Loaded {_testAssemblyPath}"); +#else + log.Debug($"Loaded {_testAssemblyPath}"); + return (string)ExecuteMethod(LOAD_METHOD); +#endif + } + + /// + /// Counts the number of test cases for the loaded test assembly + /// + /// The XML test filter + /// The number of test cases + public int CountTestCases(string filter) + { + CheckLoadWasCalled(); + return PerformCountTestCases(filter); + } + + public int PerformCountTestCases(string filter) + { +#if NETFRAMEWORK + CallbackHandler handler = new CallbackHandler(); + CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return int.Parse(handler.Result.ShouldNotBeNull()); +#else + object? count = ExecuteMethod(COUNT_METHOD, filter); + return count != null ? (int)count : 0; +#endif + } + + /// + /// Executes the tests in an assembly. + /// + /// An ITestEventHandler that receives progress notices + /// A filter that controls which tests are executed + /// An Xml string representing the result + public string Run(ITestEventListener? listener, string filter) + { + CheckLoadWasCalled(); +#if NETFRAMEWORK + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + var handler = new RunTestsCallbackHandler(listener); + CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return handler.Result.ShouldNotBeNull(); +#else + log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; + return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); +#endif + } + +#if NETCOREAPP + /// + /// Executes the tests in an assembly asynchronously. + /// + /// A callback that receives XML progress notices + /// A filter that controls which tests are executed + public void RunAsync(Action callback, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); + } +#endif + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public void StopRun(bool force) + { +#if NETFRAMEWORK + CreateObject(STOP_RUN_ACTION, _frameworkController.ShouldNotBeNull(), force, new CallbackHandler()); +#else + ExecuteMethod(STOP_RUN_METHOD, force); +#endif + } + + /// + /// Returns information about the tests in an assembly. + /// + /// A filter indicating which tests to include + /// An Xml string representing the tests + public string Explore(string filter) + { + CheckLoadWasCalled(); +#if NETFRAMEWORK + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + CallbackHandler handler = new CallbackHandler(); + CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return handler.Result.ShouldNotBeNull(); +#else + log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + return (string)ExecuteMethod(EXPLORE_METHOD, filter); +#endif + } + + private void CheckLoadWasCalled() + { + if (_frameworkController == null) + throw new InvalidOperationException(LOAD_MESSAGE); + } + +#if NETFRAMEWORK + private string ExecuteAction(string action, params object?[] args) + { + CallbackHandler handler = new CallbackHandler(); + CreateObject(action, _frameworkController, handler); + return handler.Result.ShouldNotBeNull(); + } + + private object CreateObject(string typeName, params object?[]? args) + { + try + { + return _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + } + catch (TargetInvocationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); + } + } +#else + private object CreateObject(string typeName, params object?[]? args) + { + var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; + return Activator.CreateInstance(type, args)!; + } + + private Assembly LoadAssembly(string assemblyPath) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyPath returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); + log.Error(msg); + throw new NUnitEngineException(msg, e); + } + + log.Debug($"Loaded {assemblyPath}"); + return assembly; + } + + private Assembly LoadAssembly(AssemblyName assemblyName) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyName returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); + log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); + throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); + } + + log.Debug($"Loaded {assemblyName.FullName}"); + return assembly; + } + + object ExecuteMethod(string methodName, params object?[] args) + { + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + return ExecuteMethod(method, args); + } + + object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) + { + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); + return ExecuteMethod(method, args); + } + + object ExecuteMethod(MethodInfo? method, params object?[] args) + { + if (method == null) + { + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + } + + //using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) + //{ + log.Debug($"Executing {method.DeclaringType}.{method.Name}"); + return method.Invoke(_frameworkController, args).ShouldNotBeNull(); + //} + } +#endif + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index 8142942b3..bb31a5d83 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -33,8 +33,8 @@ public bool IsSupportedTestFramework(AssemblyName reference) public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); - - return new NUnit3FrameworkDriver(domain, reference); + log.Info("Using NUnit3CombinedFrameworkDriver"); + return new NUnit3CombinedFrameworkDriver(domain, reference); } #else /// @@ -45,8 +45,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) public IFrameworkDriver GetDriver(AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); - log.Info("Using NUnitNetCore31Driver"); - return new NUnitNetCore31Driver(reference); + log.Info("Using NUnit3CombinedFrameworkDriver"); + return new NUnit3CombinedFrameworkDriver(reference); } #endif } From ec5977db46b9427d8b6c55d9ce79eca9043ae8d0 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 5 Jan 2025 17:26:34 -0800 Subject: [PATCH 05/16] Encapsulate the two APIs in separate nested classes --- .../nunit.engine.core.tests/TestData.cs | 39 -- .../Drivers/NUnit3CombinedFrameworkDriver.cs | 498 ++++++++++-------- 2 files changed, 287 insertions(+), 250 deletions(-) delete mode 100644 src/NUnitEngine/nunit.engine.core.tests/TestData.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/TestData.cs b/src/NUnitEngine/nunit.engine.core.tests/TestData.cs deleted file mode 100644 index df2896dac..000000000 --- a/src/NUnitEngine/nunit.engine.core.tests/TestData.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace NUnit.Engine -{ - internal class TestData - { -#if NETCOREAPP3_1 - const string CURRENT_RUNTIME = "netcoreapp3.1"; -#elif NET6_0 - const string CURRENT_RUNTIME = "net6.0"; -#elif NET8_0 - const string CURRENT_RUNTIME = "net8.0"; -#else - const string CURRENT_RUNTIME = "net462"; -#endif - public static string MockAssemblyPath(string runtime) - => $"testdata/{runtime}/mock-assembly.dll"; - public static string NoTestAssemblyPath(string runtime) - => $"testdata/{runtime}/notest-assembly.dll"; - - [Test] - public void SelfTest() - { - VerifyFilePath(MockAssemblyPath(CURRENT_RUNTIME)); - } - - private void VerifyFilePath(string path) - { - Assert.That(File.Exists(path), $"File not found at {path}"); - } - } -} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs index ecd069443..f4b7f7737 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs @@ -17,42 +17,16 @@ namespace NUnit.Engine.Drivers /// public class NUnit3CombinedFrameworkDriver : IFrameworkDriver { - private const string LOAD_MESSAGE = "Method called without calling Load first"; + const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; - private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; -#if NETFRAMEWORK - private static readonly string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; - private static readonly string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; - private static readonly string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; - private static readonly string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; - private static readonly string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; -#else - private static readonly string LOAD_METHOD = "LoadTests"; - private static readonly string EXPLORE_METHOD = "ExploreTests"; - private static readonly string COUNT_METHOD = "CountTests"; - private static readonly string RUN_METHOD = "RunTests"; - private static readonly string RUN_ASYNC_METHOD = "RunTests"; - private static readonly string STOP_RUN_METHOD = "StopRun"; -#endif + const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnit3CombinedFrameworkDriver)); - readonly AssemblyName _nunitRef; - string? _testAssemblyPath; - - object? _frameworkController; - Type? _frameworkControllerType; - -#if NETFRAMEWORK - readonly AppDomain _testDomain; -#else - Assembly? _testAssembly; - Assembly? _frameworkAssembly; - TestAssemblyLoadContext? _assemblyLoadContext; -#endif + readonly FrameworkApi _api; #if NETFRAMEWORK /// @@ -62,8 +36,9 @@ public class NUnit3CombinedFrameworkDriver : IFrameworkDriver /// An AssemblyName referring to the test framework. public NUnit3CombinedFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) { - _testDomain = testDomain; - _nunitRef = nunitRef; + Guard.ArgumentNotNull(nunitRef, nameof(testDomain)); + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + _api = new Api2009(this, testDomain, nunitRef); } #else /// @@ -73,7 +48,7 @@ public NUnit3CombinedFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef public NUnit3CombinedFrameworkDriver(AssemblyName nunitRef) { Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _nunitRef = nunitRef; + _api = new Api2018(this, nunitRef); } #endif @@ -90,80 +65,14 @@ public NUnit3CombinedFrameworkDriver(AssemblyName nunitRef) /// The test settings /// An XML string representing the loaded test public string Load(string testAssemblyPath, IDictionary settings) - { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - log.Debug($"Loading {testAssemblyPath}"); - var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - - // Normally, the caller should check for an invalid requested runtime, but we make sure here - var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) - ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - - _testAssemblyPath = Path.GetFullPath(testAssemblyPath); -#if NETFRAMEWORK - try - { - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); - } - catch (BadImageFormatException ex) when (requestedRuntime != null) - { - throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); - } - catch (SerializationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); - } -#else - _assemblyLoadContext = new TestAssemblyLoadContext(_testAssemblyPath); - - _testAssembly = LoadAssembly(_testAssemblyPath!); - _frameworkAssembly = LoadAssembly(_nunitRef); - - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); - if (_frameworkController == null) - { - log.Error(INVALID_FRAMEWORK_MESSAGE); - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } -#endif - - _frameworkControllerType = _frameworkController.GetType(); - log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); - - var fileName = Path.GetFileName(_testAssemblyPath); - log.Info("Loading {0} - see separate log file", fileName); - -#if NETFRAMEWORK - return ExecuteAction(LOAD_ACTION); - //log.Debug($"Loaded {_testAssemblyPath}"); -#else - log.Debug($"Loaded {_testAssemblyPath}"); - return (string)ExecuteMethod(LOAD_METHOD); -#endif - } + => _api.Load(testAssemblyPath, settings); /// /// Counts the number of test cases for the loaded test assembly /// /// The XML test filter /// The number of test cases - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - return PerformCountTestCases(filter); - } - - public int PerformCountTestCases(string filter) - { -#if NETFRAMEWORK - CallbackHandler handler = new CallbackHandler(); - CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return int.Parse(handler.Result.ShouldNotBeNull()); -#else - object? count = ExecuteMethod(COUNT_METHOD, filter); - return count != null ? (int)count : 0; -#endif - } + public int CountTestCases(string filter) => _api.CountTestCases(filter); /// /// Executes the tests in an assembly. @@ -171,167 +80,334 @@ public int PerformCountTestCases(string filter) /// An ITestEventHandler that receives progress notices /// A filter that controls which tests are executed /// An Xml string representing the result - public string Run(ITestEventListener? listener, string filter) - { - CheckLoadWasCalled(); -#if NETFRAMEWORK - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - var handler = new RunTestsCallbackHandler(listener); - CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); -#else - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; - return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); -#endif - } + public string Run(ITestEventListener? listener, string filter) => _api.Run(listener, filter); -#if NETCOREAPP /// /// Executes the tests in an assembly asynchronously. /// /// A callback that receives XML progress notices /// A filter that controls which tests are executed - public void RunAsync(Action callback, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } -#endif + public void RunAsync(Action callback, string filter) => _api.RunAsync(callback, filter); /// /// Cancel the ongoing test run. If no test is running, the call is ignored. /// /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public void StopRun(bool force) - { -#if NETFRAMEWORK - CreateObject(STOP_RUN_ACTION, _frameworkController.ShouldNotBeNull(), force, new CallbackHandler()); -#else - ExecuteMethod(STOP_RUN_METHOD, force); -#endif - } + public void StopRun(bool force) => _api.StopRun(force); /// /// Returns information about the tests in an assembly. /// /// A filter indicating which tests to include /// An Xml string representing the tests - public string Explore(string filter) - { - CheckLoadWasCalled(); -#if NETFRAMEWORK - log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - CallbackHandler handler = new CallbackHandler(); - CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); -#else - log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - return (string)ExecuteMethod(EXPLORE_METHOD, filter); -#endif - } - - private void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } + public string Explore(string filter) => _api.Explore(filter); #if NETFRAMEWORK - private string ExecuteAction(string action, params object?[] args) + /// + /// This is the original NUnit 3 API, which only works for .NET Framework. + /// As far as I can discover, it first appeared in pre-release 2.9.1, + /// on launchpad in 2009, hence the name. + /// + class Api2009 : FrameworkApi { - CallbackHandler handler = new CallbackHandler(); - CreateObject(action, _frameworkController, handler); - return handler.Result.ShouldNotBeNull(); - } + NUnit3CombinedFrameworkDriver _driver; - private object CreateObject(string typeName, params object?[]? args) - { - try + AppDomain _testDomain; + AssemblyName _nunitRef; + + string? _testAssemblyPath; + + object? _frameworkController; + Type? _frameworkControllerType; + + public Api2009(NUnit3CombinedFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + { + _driver = driver; + _testDomain = testDomain; + _nunitRef = nunitRef; + } + + public string Load(string testAssemblyPath, IDictionary settings) + { + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + + log.Info($"Loading {testAssemblyPath} - see separate log file"); + + _testAssemblyPath = testAssemblyPath; + + // Normally, the caller should check for an invalid requested runtime, but we make sure here + var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) + ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; + + var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; + try + { + _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); + } + catch (BadImageFormatException ex) when (requestedRuntime != null) + { + throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); + } + catch (SerializationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); + } + + _frameworkControllerType = _frameworkController.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); + + return ExecuteAction(LOAD_ACTION); + //log.Debug($"Loaded {_testAssemblyPath}"); + } + + public int CountTestCases(string filter) + { + CheckLoadWasCalled(); + CallbackHandler handler = new CallbackHandler(); + CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return int.Parse(handler.Result.ShouldNotBeNull()); + } + + public string Run(ITestEventListener? listener, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + var handler = new RunTestsCallbackHandler(listener); + CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return handler.Result.ShouldNotBeNull(); + } + + public void RunAsync(Action callback, string filter) => throw new NotImplementedException(); + + public void StopRun(bool force) { - return _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + CreateObject(STOP_RUN_ACTION, _frameworkController.ShouldNotBeNull(), force, new CallbackHandler()); } - catch (TargetInvocationException ex) + + public string Explore(string filter) { - throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); + CheckLoadWasCalled(); + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + CallbackHandler handler = new CallbackHandler(); + CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); + return handler.Result.ShouldNotBeNull(); + } + + public void CheckLoadWasCalled() + { + if (_frameworkController == null) + throw new InvalidOperationException(LOAD_MESSAGE); + } + + const string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; + const string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; + const string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; + const string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; + const string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; + + public string ExecuteAction(string action, params object?[] args) + { + CallbackHandler handler = new CallbackHandler(); + CreateObject(action, _frameworkController, handler); + return handler.Result.ShouldNotBeNull(); + } + + public object CreateObject(string typeName, params object?[]? args) + { + try + { + return _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + } + catch (TargetInvocationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); + } } } #else - private object CreateObject(string typeName, params object?[]? args) - { - var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; - return Activator.CreateInstance(type, args)!; - } - - private Assembly LoadAssembly(string assemblyPath) + /// + /// This is the revised API, designed for use with .NET Core. It first + /// appears in our source code in 2018. + /// + class Api2018 : FrameworkApi { - Assembly assembly; + NUnit3CombinedFrameworkDriver _driver; + AssemblyName _nunitRef; + + string? _testAssemblyPath; + TestAssemblyLoadContext? _assemblyLoadContext; + Assembly? _testAssembly; + Assembly? _frameworkAssembly; - try + object? _frameworkController; + Type? _frameworkControllerType; + + public Api2018(NUnit3CombinedFrameworkDriver driver, AssemblyName nunitRef) { - assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyPath returned null"); + _driver = driver; + _nunitRef = nunitRef; } - catch (Exception e) + + public string Load(string testAssemblyPath, IDictionary settings) { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); - log.Error(msg); - throw new NUnitEngineException(msg, e); + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + + log.Info($"Loading {testAssemblyPath} - see separate log file"); + + _testAssemblyPath = Path.GetFullPath(testAssemblyPath); + + _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); + + _testAssembly = LoadAssembly(testAssemblyPath); + _frameworkAssembly = LoadAssembly(_nunitRef); + + var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; + _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); + if (_frameworkController == null) + { + log.Error(INVALID_FRAMEWORK_MESSAGE); + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + } + + _frameworkControllerType = _frameworkController.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); + + log.Debug($"Loaded {testAssemblyPath}"); + return (string)ExecuteMethod(LOAD_METHOD); } - log.Debug($"Loaded {assemblyPath}"); - return assembly; - } + public int CountTestCases(string filter) + { + CheckLoadWasCalled(); + object? count = ExecuteMethod(COUNT_METHOD, filter); + return count != null ? (int)count : 0; + } - private Assembly LoadAssembly(AssemblyName assemblyName) - { - Assembly assembly; + public string Run(ITestEventListener? listener, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; + return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); + } - try + public void RunAsync(Action callback, string filter) { - assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyName returned null"); + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); } - catch (Exception e) + + public void StopRun(bool force) { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); - log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); + ExecuteMethod(STOP_RUN_METHOD, force); } - log.Debug($"Loaded {assemblyName.FullName}"); - return assembly; - } + public string Explore(string filter) + { + CheckLoadWasCalled(); + log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + return (string)ExecuteMethod(EXPLORE_METHOD, filter); + } - object ExecuteMethod(string methodName, params object?[] args) - { - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); - return ExecuteMethod(method, args); - } + public void CheckLoadWasCalled() + { + if (_frameworkController == null) + throw new InvalidOperationException(LOAD_MESSAGE); + } - object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) - { - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); - return ExecuteMethod(method, args); - } - object ExecuteMethod(MethodInfo? method, params object?[] args) - { - if (method == null) + public object CreateObject(string typeName, params object?[]? args) + { + var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; + return Activator.CreateInstance(type, args)!; + } + + public Assembly LoadAssembly(string assemblyPath) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyPath returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); + log.Error(msg); + throw new NUnitEngineException(msg, e); + } + + log.Debug($"Loaded {assemblyPath}"); + return assembly; + } + + public Assembly LoadAssembly(AssemblyName assemblyName) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyName returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); + log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); + throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); + } + + log.Debug($"Loaded {assemblyName.FullName}"); + return assembly; + } + + private static readonly string LOAD_METHOD = "LoadTests"; + private static readonly string EXPLORE_METHOD = "ExploreTests"; + private static readonly string COUNT_METHOD = "CountTests"; + private static readonly string RUN_METHOD = "RunTests"; + private static readonly string RUN_ASYNC_METHOD = "RunTests"; + private static readonly string STOP_RUN_METHOD = "StopRun"; + + public object ExecuteMethod(string methodName, params object?[] args) { - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + return ExecuteMethod(method, args); } - //using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) - //{ + public object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) + { + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); + return ExecuteMethod(method, args); + } + + public object ExecuteMethod(MethodInfo? method, params object?[] args) + { + if (method == null) + { + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + } + + //using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) + //{ log.Debug($"Executing {method.DeclaringType}.{method.Name}"); return method.Invoke(_frameworkController, args).ShouldNotBeNull(); - //} + //} + } } #endif + interface FrameworkApi + { + string Load(string testAssemblyPath, IDictionary settings); + int CountTestCases(string filter); + string Run(ITestEventListener? listener, string filter); + void RunAsync(Action callback, string filter); + void StopRun(bool force); + string Explore(string filter); + } } } From 26bb9994c47de9b8c4f1e167cd9347100f7f6b1a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Wed, 8 Jan 2025 05:23:32 -0800 Subject: [PATCH 06/16] Refactor and remove all classes --- ...rTests.cs => NUnitFrameworkDriverTests.cs} | 20 +- .../Services/DriverServiceTests.cs | 6 +- .../Drivers/NUnit3DriverFactory.cs | 8 +- .../Drivers/NUnit3FrameworkDriver.cs | 176 ------------- ...eworkDriver.cs => NUnitFrameworkDriver.cs} | 138 +++++----- .../Drivers/NUnitNetCore31Driver.cs | 239 ------------------ .../Services/TestFilteringTests.cs | 10 +- 7 files changed, 98 insertions(+), 499 deletions(-) rename src/NUnitEngine/nunit.engine.core.tests/Drivers/{NUnit3FrameworkDriverTests.cs => NUnitFrameworkDriverTests.cs} (93%) delete mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs rename src/NUnitEngine/nunit.engine.core/Drivers/{NUnit3CombinedFrameworkDriver.cs => NUnitFrameworkDriver.cs} (80%) delete mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs similarity index 93% rename from src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs rename to src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs index 43d6b0b42..166fad528 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnit3FrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if NETFRAMEWORK using System; using System.Collections.Generic; using System.Reflection; @@ -13,22 +12,26 @@ namespace NUnit.Engine.Drivers { // Functional tests of the NUnitFrameworkDriver calling into the framework. - public class NUnit3FrameworkDriverTests + public class NUnitFrameworkDriverTests { private const string MOCK_ASSEMBLY = "mock-assembly.dll"; - private const string LOAD_MESSAGE = "Method called without calling Load first"; + private const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; private IDictionary _settings = new Dictionary(); - private NUnit3FrameworkDriver _driver; + private NUnitFrameworkDriver _driver; private string _mockAssemblyPath; [SetUp] public void CreateDriver() { - var assemblyName = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); + var nunitRef = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); _mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); - _driver = new NUnit3FrameworkDriver(AppDomain.CurrentDomain, assemblyName); +#if NETFRAMEWORK + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, nunitRef); +#else + _driver = new NUnitFrameworkDriver(nunitRef); +#endif } [Test] @@ -109,7 +112,8 @@ public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); } - [Test] +#if NETFRAMEWORK + //[Test] public void RunTestsAction_WithInvalidFilterElement_ThrowsNUnitEngineException() { _driver.Load(_mockAssemblyPath, _settings); @@ -133,6 +137,7 @@ public void RaiseCallbackEvent(string eventArgument) _result = eventArgument; } } +#endif public class NullListener : ITestEventListener { @@ -143,4 +148,3 @@ public void OnTestEvent(string testEvent) } } } -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index 204b45560..a4be7785e 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -30,9 +30,9 @@ public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Typ static TestCaseData[] DriverSelectionTestCases = new[] { // TODO: make commented tests work - new TestCaseData("mock-assembly.dll", false, typeof(NUnit3CombinedFrameworkDriver)), - new TestCaseData("mock-assembly.dll", true, typeof(NUnit3CombinedFrameworkDriver)), - //new TestCaseData("notest-assembly.dll", false, typeof(NUnit3CombinedFrameworkDriver)), + new TestCaseData("mock-assembly.dll", false, typeof(NUnitFrameworkDriver)), + new TestCaseData("mock-assembly.dll", true, typeof(NUnitFrameworkDriver)), + //new TestCaseData("notest-assembly.dll", false, typeof(NUnitFrameworkDriver)), //new TestCaseData"notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)) // Invalid cases should work with all target runtimes diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index bb31a5d83..1ab6f009b 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -33,8 +33,8 @@ public bool IsSupportedTestFramework(AssemblyName reference) public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); - log.Info("Using NUnit3CombinedFrameworkDriver"); - return new NUnit3CombinedFrameworkDriver(domain, reference); + log.Info("Using NUnitFrameworkDriver"); + return new NUnitFrameworkDriver(domain, reference); } #else /// @@ -45,8 +45,8 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) public IFrameworkDriver GetDriver(AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); - log.Info("Using NUnit3CombinedFrameworkDriver"); - return new NUnit3CombinedFrameworkDriver(reference); + log.Info("Using NUnitFrameworkDriver"); + return new NUnitFrameworkDriver(reference); } #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs deleted file mode 100644 index 285863788..000000000 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3FrameworkDriver.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETFRAMEWORK -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using NUnit.Common; -using NUnit.Engine.Internal; -using NUnit.Engine.Extensibility; - -namespace NUnit.Engine.Drivers -{ - /// - /// NUnitFrameworkDriver is used by the test-runner to load and run - /// tests using the NUnit framework assembly. - /// - public class NUnit3FrameworkDriver : IFrameworkDriver - { - // Messages - private const string LOAD_MESSAGE = "Method called without calling Load first"; - - // API Constants - private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - private static readonly string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; - private static readonly string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; - private static readonly string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; - private static readonly string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; - private static readonly string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; - - static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnit3FrameworkDriver)); - - readonly AppDomain _testDomain; - readonly AssemblyName _nunitRef; - string? _testAssemblyPath; - - object? _frameworkController; - Type? _frameworkControllerType; - - /// - /// Construct an NUnit3FrameworkDriver - /// - /// The application domain in which to create the FrameworkController - /// An AssemblyName referring to the test framework. - public NUnit3FrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) - { - _testDomain = testDomain; - _nunitRef = nunitRef; - } - - /// - /// An id prefix that will be passed to the test framework and used as part of the - /// test ids created. - /// - public string ID { get; set; } = string.Empty; - - /// - /// Loads the tests in an assembly. - /// - /// The path to the test assembly - /// The test settings - /// An XML string representing the loaded test - public string Load(string testAssemblyPath, IDictionary settings) - { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - log.Debug($"Loading {testAssemblyPath}"); - var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - - // Normally, the caller should check for an invalid requested runtime, but we make sure here - var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) - ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - - _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - - try - { - _frameworkController = CreateObject(CONTROLLER_TYPE, testAssemblyPath, idPrefix, settings); - } - catch (BadImageFormatException ex) when (requestedRuntime != null) - { - throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {testAssemblyPath}", ex); - } - catch (SerializationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); - } - - _frameworkControllerType = _frameworkController.GetType(); - log.Debug($"Created FrameworkControler {_frameworkControllerType.Name}"); - - CallbackHandler handler = new CallbackHandler(); - - var fileName = Path.GetFileName(_testAssemblyPath); - - log.Info("Loading {0} - see separate log file", fileName); - - CreateObject(LOAD_ACTION, _frameworkController, handler); - - log.Debug($"Loaded {testAssemblyPath}"); - - return handler.Result.ShouldNotBeNull(); - } - - /// - /// Counts the number of test cases for the loaded test assembly - /// - /// The XML test filter - /// The number of test cases - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - CallbackHandler handler = new CallbackHandler(); - CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return int.Parse(handler.Result.ShouldNotBeNull()); - } - - /// - /// Executes the tests in an assembly. - /// - /// An ITestEventHandler that receives progress notices - /// A filter that controls which tests are executed - /// An Xml string representing the result - public string Run(ITestEventListener? listener, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - var handler = new RunTestsCallbackHandler(listener); - CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); - } - - /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. - /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public void StopRun(bool force) - { - CreateObject(STOP_RUN_ACTION, _frameworkController.ShouldNotBeNull(), force, new CallbackHandler()); - } - - /// - /// Returns information about the tests in an assembly. - /// - /// A filter indicating which tests to include - /// An Xml string representing the tests - public string Explore(string filter) - { - CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - CallbackHandler handler = new CallbackHandler(); - CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); - } - - private void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } - - private object CreateObject(string typeName, params object?[]? args) - { - try - { - return _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, typeName, false, 0, null, args, null, null )!; - } - catch (TargetInvocationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); - } - } - } -} -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs similarity index 80% rename from src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs rename to src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index f4b7f7737..4a8423674 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3CombinedFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -12,10 +12,10 @@ namespace NUnit.Engine.Drivers { /// - /// NUnit3CombinedFrameworkDriver is used by the test-runner to load and run - /// tests using the NUnit framework assembly. + /// NUnitFrameworkDriver is used by the test-runner to load and run + /// tests using the NUnit framework assembly, versions 3 and up. /// - public class NUnit3CombinedFrameworkDriver : IFrameworkDriver + public class NUnitFrameworkDriver : IFrameworkDriver { const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; @@ -24,28 +24,30 @@ public class NUnit3CombinedFrameworkDriver : IFrameworkDriver const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnit3CombinedFrameworkDriver)); + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkDriver)); readonly FrameworkApi _api; #if NETFRAMEWORK /// - /// Construct an NUnit3CombinedFrameworkDriver + /// Construct an NUnitFrameworkDriver /// /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. - public NUnit3CombinedFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) + /// Api to use, either "2018" or "2009". Provided for testing. + public NUnitFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) { - Guard.ArgumentNotNull(nunitRef, nameof(testDomain)); + Guard.ArgumentNotNull(testDomain, nameof(testDomain)); Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _api = new Api2009(this, testDomain, nunitRef); + + _api = new Api2009(this, testDomain, nunitRef); } #else /// - /// Construct an NUnitNetCore31Driver + /// Construct an NUnitFrameworkDriver /// /// An AssemblyName referring to the test framework. - public NUnit3CombinedFrameworkDriver(AssemblyName nunitRef) + public NUnitFrameworkDriver(AssemblyName nunitRef) { Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); _api = new Api2018(this, nunitRef); @@ -110,7 +112,7 @@ public string Load(string testAssemblyPath, IDictionary settings /// class Api2009 : FrameworkApi { - NUnit3CombinedFrameworkDriver _driver; + NUnitFrameworkDriver _driver; AppDomain _testDomain; AssemblyName _nunitRef; @@ -120,8 +122,8 @@ class Api2009 : FrameworkApi object? _frameworkController; Type? _frameworkControllerType; - public Api2009(NUnit3CombinedFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) - { + public Api2009(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + { _driver = driver; _testDomain = testDomain; _nunitRef = nunitRef; @@ -157,62 +159,68 @@ public string Load(string testAssemblyPath, IDictionary settings log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); return ExecuteAction(LOAD_ACTION); - //log.Debug($"Loaded {_testAssemblyPath}"); } public int CountTestCases(string filter) { CheckLoadWasCalled(); - CallbackHandler handler = new CallbackHandler(); - CreateObject(COUNT_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return int.Parse(handler.Result.ShouldNotBeNull()); + return int.Parse(ExecuteAction(COUNT_ACTION, filter)); } public string Run(ITestEventListener? listener, string filter) { CheckLoadWasCalled(); log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - var handler = new RunTestsCallbackHandler(listener); - CreateObject(RUN_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); + return ExecuteAction(RUN_ACTION, listener, filter); } public void RunAsync(Action callback, string filter) => throw new NotImplementedException(); - public void StopRun(bool force) - { - CreateObject(STOP_RUN_ACTION, _frameworkController.ShouldNotBeNull(), force, new CallbackHandler()); - } + public void StopRun(bool force) => ExecuteAction(STOP_RUN_ACTION, force); public string Explore(string filter) { CheckLoadWasCalled(); log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - CallbackHandler handler = new CallbackHandler(); - CreateObject(EXPLORE_ACTION, _frameworkController.ShouldNotBeNull(), filter, handler); - return handler.Result.ShouldNotBeNull(); + return ExecuteAction(EXPLORE_ACTION, filter); } - public void CheckLoadWasCalled() + private void CheckLoadWasCalled() { if (_frameworkController == null) throw new InvalidOperationException(LOAD_MESSAGE); } + // Actions with no extra arguments beyond controller and handler const string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; + private string ExecuteAction(string action) + { + CallbackHandler handler = new CallbackHandler(); + CreateObject(action, _frameworkController, handler); + return handler.Result.ShouldNotBeNull(); + } + + // Actions with one extra argument const string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; const string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; - const string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; const string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; - - public string ExecuteAction(string action, params object?[] args) + private string ExecuteAction(string action, object arg1) { CallbackHandler handler = new CallbackHandler(); - CreateObject(action, _frameworkController, handler); + CreateObject(action, _frameworkController, arg1, handler); + return handler.Result.ShouldNotBeNull(); + } + + // Run action has two extra arguments and uses a special handler + const string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; + private string ExecuteAction(string action, ITestEventListener? listener, string filter) + { + RunTestsCallbackHandler handler = new RunTestsCallbackHandler(listener); + CreateObject(action, _frameworkController, filter, handler); return handler.Result.ShouldNotBeNull(); } - public object CreateObject(string typeName, params object?[]? args) + private object CreateObject(string typeName, params object?[]? args) { try { @@ -228,14 +236,17 @@ public object CreateObject(string typeName, params object?[]? args) #else /// /// This is the revised API, designed for use with .NET Core. It first - /// appears in our source code in 2018. + /// appears in our source code in 2018. This implementation is modified + /// to make it work under the .NET Framework as well as .NET Core. It + /// may be used for NUnit 3.10 or higher. /// class Api2018 : FrameworkApi { - NUnit3CombinedFrameworkDriver _driver; - AssemblyName _nunitRef; + NUnitFrameworkDriver _driver; + AssemblyName _nunitRef; string? _testAssemblyPath; + TestAssemblyLoadContext? _assemblyLoadContext; Assembly? _testAssembly; Assembly? _frameworkAssembly; @@ -243,7 +254,7 @@ class Api2018 : FrameworkApi object? _frameworkController; Type? _frameworkControllerType; - public Api2018(NUnit3CombinedFrameworkDriver driver, AssemblyName nunitRef) + public Api2018(NUnitFrameworkDriver driver, AssemblyName nunitRef) { _driver = driver; _nunitRef = nunitRef; @@ -252,26 +263,24 @@ public Api2018(NUnit3CombinedFrameworkDriver driver, AssemblyName nunitRef) public string Load(string testAssemblyPath, IDictionary settings) { Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - log.Info($"Loading {testAssemblyPath} - see separate log file"); _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - + var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); _testAssembly = LoadAssembly(testAssemblyPath); _frameworkAssembly = LoadAssembly(_nunitRef); - var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); + _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); if (_frameworkController == null) { log.Error(INVALID_FRAMEWORK_MESSAGE); throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); } - _frameworkControllerType = _frameworkController.GetType(); - log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); + _frameworkControllerType = _frameworkController?.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType?.Name}"); log.Debug($"Loaded {testAssemblyPath}"); return (string)ExecuteMethod(LOAD_METHOD); @@ -287,7 +296,7 @@ public int CountTestCases(string filter) public string Run(ITestEventListener? listener, string filter) { CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); } @@ -295,7 +304,7 @@ public string Run(ITestEventListener? listener, string filter) public void RunAsync(Action callback, string filter) { CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); } @@ -307,24 +316,23 @@ public void StopRun(bool force) public string Explore(string filter) { CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); return (string)ExecuteMethod(EXPLORE_METHOD, filter); } - public void CheckLoadWasCalled() + private void CheckLoadWasCalled() { if (_frameworkController == null) throw new InvalidOperationException(LOAD_MESSAGE); } - - public object CreateObject(string typeName, params object?[]? args) + private object CreateInstance(string typeName, params object?[]? args) { var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; return Activator.CreateInstance(type, args)!; } - public Assembly LoadAssembly(string assemblyPath) + private Assembly LoadAssembly(string assemblyPath) { Assembly assembly; @@ -345,7 +353,7 @@ public Assembly LoadAssembly(string assemblyPath) return assembly; } - public Assembly LoadAssembly(AssemblyName assemblyName) + private Assembly LoadAssembly(AssemblyName assemblyName) { Assembly assembly; @@ -366,40 +374,46 @@ public Assembly LoadAssembly(AssemblyName assemblyName) return assembly; } + // API methods with no overloads private static readonly string LOAD_METHOD = "LoadTests"; private static readonly string EXPLORE_METHOD = "ExploreTests"; private static readonly string COUNT_METHOD = "CountTests"; - private static readonly string RUN_METHOD = "RunTests"; - private static readonly string RUN_ASYNC_METHOD = "RunTests"; private static readonly string STOP_RUN_METHOD = "StopRun"; - public object ExecuteMethod(string methodName, params object?[] args) + // Execute methods with no overloads + private object ExecuteMethod(string methodName, params object?[] args) { + log.Debug($"Calling API method {methodName} with args {string.Join("+", args)}"); var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); return ExecuteMethod(method, args); } - public object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) + // API methods with overloads + private static readonly string RUN_METHOD = "RunTests"; + private static readonly string RUN_ASYNC_METHOD = "RunTests"; + + // Execute overloaded methods specifying argument types + private object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) { + log.Debug($"Calling API method {methodName} with arg types {string.Join("+", ptypes)}"); var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); return ExecuteMethod(method, args); } - public object ExecuteMethod(MethodInfo? method, params object?[] args) + private object ExecuteMethod(MethodInfo? method, params object?[] args) { if (method == null) - { throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } - //using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) - //{ log.Debug($"Executing {method.DeclaringType}.{method.Name}"); - return method.Invoke(_frameworkController, args).ShouldNotBeNull(); - //} + using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) + { + return method.Invoke(_frameworkController, args).ShouldNotBeNull(); + } } } #endif + interface FrameworkApi { string Load(string testAssemblyPath, IDictionary settings); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs deleted file mode 100644 index a0712cead..000000000 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitNetCore31Driver.cs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt - -#if NETCOREAPP -using System; -using System.Linq; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using NUnit.Common; -using NUnit.Engine.Internal; -using NUnit.Engine.Extensibility; - -namespace NUnit.Engine.Drivers -{ - /// - /// NUnitNetCore31Driver is used by the test-runner to load and run - /// tests using the NUnit framework assembly. It contains functionality to - /// correctly load assemblies from other directories, using APIs first available in - /// .NET Core 3.1. - /// - public class NUnitNetCore31Driver : IFrameworkDriver - { - private const string LOAD_MESSAGE = "Method called without calling Load first"; - const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; - const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; - const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; - - private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - private static readonly string LOAD_METHOD = "LoadTests"; - private static readonly string EXPLORE_METHOD = "ExploreTests"; - private static readonly string COUNT_METHOD = "CountTests"; - private static readonly string RUN_METHOD = "RunTests"; - private static readonly string RUN_ASYNC_METHOD = "RunTests"; - private static readonly string STOP_RUN_METHOD = "StopRun"; - - static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitNetCore31Driver)); - - readonly AssemblyName _nunitRef; - string? _testAssemblyPath; - - object? _frameworkController; - Type? _frameworkControllerType; - Assembly? _testAssembly; - Assembly? _frameworkAssembly; - TestAssemblyLoadContext? _assemblyLoadContext; - - /// - /// Construct an NUnitNetCore31Driver - /// - /// An AssemblyName referring to the test framework. - public NUnitNetCore31Driver(AssemblyName nunitRef) - { - Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _nunitRef = nunitRef; - } - - /// - /// An id prefix that will be passed to the test framework and used as part of the - /// test ids created. - /// - public string ID { get; set; } = string.Empty; - - /// - /// Loads the tests in an assembly. - /// - /// The path to the test assembly - /// The test settings - /// An XML string representing the loaded test - public string Load(string testAssemblyPath, IDictionary settings) - { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - log.Debug($"Loading {testAssemblyPath}"); - var idPrefix = string.IsNullOrEmpty(ID) ? "" : ID + "-"; - - // Normally, the caller should check for an invalid requested runtime, but we make sure here - var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) - ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - - _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - _assemblyLoadContext = new TestAssemblyLoadContext(_testAssemblyPath); - - _testAssembly = LoadAssembly(_testAssemblyPath!); - _frameworkAssembly = LoadAssembly(_nunitRef); - - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); - if (_frameworkController == null) - { - log.Error(INVALID_FRAMEWORK_MESSAGE); - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } - - _frameworkControllerType = _frameworkController.GetType(); - log.Debug($"Created FrameworkControler {_frameworkControllerType.Name}"); - - log.Info("Loading {0} - see separate log file", _testAssembly.FullName!); - return (string)ExecuteMethod(LOAD_METHOD); - } - - /// - /// Counts the number of test cases for the loaded test assembly - /// - /// The XML test filter - /// The number of test cases - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - object? count = ExecuteMethod(COUNT_METHOD, filter); - return count != null ? (int)count : 0; - } - - /// - /// Executes the tests in an assembly. - /// - /// An ITestEventHandler that receives progress notices - /// A filter that controls which tests are executed - /// An Xml string representing the result - public string Run(ITestEventListener? listener, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; - return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } - - /// - /// Executes the tests in an assembly asynchronously. - /// - /// A callback that receives XML progress notices - /// A filter that controls which tests are executed - public void RunAsync(Action callback, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } - - /// - /// Cancel the ongoing test run. If no test is running, the call is ignored. - /// - /// If true, cancel any ongoing test threads, otherwise wait for them to complete. - public void StopRun(bool force) - { - ExecuteMethod(STOP_RUN_METHOD, force); - } - - /// - /// Returns information about the tests in an assembly. - /// - /// A filter indicating which tests to include - /// An Xml string representing the tests - public string Explore(string filter) - { - CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", _testAssembly.ShouldNotBeNull().FullName!); - return (string)ExecuteMethod(EXPLORE_METHOD, filter); - } - - private void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } - - private object CreateObject(string typeName, params object?[]? args) - { - var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; - return Activator.CreateInstance(type, args)!; - } - - private Assembly LoadAssembly(string assemblyPath) - { - Assembly assembly; - - try - { - assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyPath returned null"); - } - catch (Exception e) - { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); - log.Error(msg); - throw new NUnitEngineException(msg, e); - } - - log.Debug($"Loaded {assemblyPath}"); - return assembly; - } - - private Assembly LoadAssembly(AssemblyName assemblyName) - { - Assembly assembly; - - try - { - assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyName returned null"); - } - catch (Exception e) - { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); - log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); - } - - log.Debug($"Loaded {assemblyName.FullName}"); - return assembly; - } - - object ExecuteMethod(string methodName, params object?[] args) - { - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); - return ExecuteMethod(method, args); - } - - object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) - { - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); - return ExecuteMethod(method, args); - } - - object ExecuteMethod(MethodInfo? method, params object?[] args) - { - if (method == null) - { - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } - - using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) - { - log.Debug($"Executing {method.DeclaringType}.{method.Name}"); - return method.Invoke(_frameworkController, args).ShouldNotBeNull(); - } - } - } -} -#endif \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs index 9c16b00bf..3fe99c6cd 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs @@ -14,11 +14,7 @@ public class TestFilteringTests { private const string MOCK_ASSEMBLY = "mock-assembly.dll"; -#if NETCOREAPP3_1_OR_GREATER - private NUnitNetCore31Driver _driver; -#else - private NUnit3FrameworkDriver _driver; -#endif + private NUnitFrameworkDriver _driver; [SetUp] public void LoadAssembly() @@ -26,9 +22,9 @@ public void LoadAssembly() var mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); var assemblyName = typeof(TestAttribute).Assembly.GetName(); #if NETCOREAPP3_1_OR_GREATER - _driver = new NUnitNetCore31Driver(assemblyName); + _driver = new NUnitFrameworkDriver(assemblyName); #else - _driver = new NUnit3FrameworkDriver(AppDomain.CurrentDomain, assemblyName); + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, assemblyName); #endif _driver.Load(mockAssemblyPath, new Dictionary()); } From 728b06606f76ffbeb09c9cc610b77f7869197118 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 13 Jan 2025 17:25:40 -0800 Subject: [PATCH 07/16] Separate files for each API version --- .../Drivers/NUnitFrameworkApi.cs | 46 +++ .../Drivers/NUnitFrameworkApi2009.cs | 152 ++++++++ .../Drivers/NUnitFrameworkApi2018.cs | 199 +++++++++++ .../Drivers/NUnitFrameworkDriver.cs | 335 +----------------- 4 files changed, 401 insertions(+), 331 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs create mode 100644 src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs new file mode 100644 index 000000000..d84a05cf3 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs @@ -0,0 +1,46 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; + +namespace NUnit.Engine.Drivers +{ + public interface NUnitFrameworkApi + { + /// + /// Loads the tests in an assembly. + /// + /// An Xml string representing the loaded test + string Load(string testAssemblyPath, IDictionary settings); + + /// + /// Count the test cases that would be executed. + /// + /// An XML string representing the TestFilter to use in counting the tests + /// The number of test cases counted + int CountTestCases(string filter); + + /// + /// Executes the tests in an assembly. + /// + /// An ITestEventHandler that receives progress notices + /// A XML string representing the filter that controls which tests are executed + /// An Xml string representing the result + string Run(ITestEventListener? listener, string filter); + + void RunAsync(Action callback, string filter); + + /// + /// Returns information about the tests in an assembly. + /// + /// An XML string representing the filter that controls which tests are included + /// An Xml string representing the tests + string Explore(string filter); + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + void StopRun(bool force); + } +} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs new file mode 100644 index 000000000..36ad19129 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs @@ -0,0 +1,152 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using NUnit.Common; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Drivers +{ +#if NETFRAMEWORK + /// + /// This is the original NUnit 3 API, which only works for .NET Framework. + /// As far as I can discover, it first appeared in pre-release 2.9.1, + /// on launchpad in 2009, hence the name. + /// + class NUnitFrameworkApi2009 : NUnitFrameworkApi + { + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkApi2009)); + + const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; + const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; + const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; + const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; + + const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; + + NUnitFrameworkDriver _driver; + + AppDomain _testDomain; + AssemblyName _nunitRef; + + string? _testAssemblyPath; + + object? _frameworkController; + Type? _frameworkControllerType; + + public NUnitFrameworkApi2009(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + { + _driver = driver; + _testDomain = testDomain; + _nunitRef = nunitRef; + } + + public string Load(string testAssemblyPath, IDictionary settings) + { + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + + log.Info($"Loading {testAssemblyPath} - see separate log file"); + + _testAssemblyPath = testAssemblyPath; + + // Normally, the caller should check for an invalid requested runtime, but we make sure here + var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) + ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; + + var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; + try + { + _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); + } + catch (BadImageFormatException ex) when (requestedRuntime != null) + { + throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); + } + catch (SerializationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); + } + + _frameworkControllerType = _frameworkController.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); + + return ExecuteAction(LOAD_ACTION); + } + + public int CountTestCases(string filter) + { + CheckLoadWasCalled(); + return int.Parse(ExecuteAction(COUNT_ACTION, filter)); + } + + public string Run(ITestEventListener? listener, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + return ExecuteAction(RUN_ACTION, listener, filter); + } + + public void RunAsync(Action callback, string filter) => throw new NotImplementedException(); + + public void StopRun(bool force) => ExecuteAction(STOP_RUN_ACTION, force); + + public string Explore(string filter) + { + CheckLoadWasCalled(); + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + return ExecuteAction(EXPLORE_ACTION, filter); + } + + private void CheckLoadWasCalled() + { + if (_frameworkController == null) + throw new InvalidOperationException(LOAD_MESSAGE); + } + + // Actions with no extra arguments beyond controller and handler + const string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; + private string ExecuteAction(string action) + { + CallbackHandler handler = new CallbackHandler(); + CreateObject(action, _frameworkController, handler); + return handler.Result.ShouldNotBeNull(); + } + + // Actions with one extra argument + const string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; + const string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; + const string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; + private string ExecuteAction(string action, object arg1) + { + CallbackHandler handler = new CallbackHandler(); + CreateObject(action, _frameworkController, arg1, handler); + return handler.Result.ShouldNotBeNull(); + } + + // Run action has two extra arguments and uses a special handler + const string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; + private string ExecuteAction(string action, ITestEventListener? listener, string filter) + { + RunTestsCallbackHandler handler = new RunTestsCallbackHandler(listener); + CreateObject(action, _frameworkController, filter, handler); + return handler.Result.ShouldNotBeNull(); + } + + private object CreateObject(string typeName, params object?[]? args) + { + try + { + return _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + } + catch (TargetInvocationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); + } + } + } +#endif +} diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs new file mode 100644 index 000000000..d6e4cdb05 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs @@ -0,0 +1,199 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETCOREAPP +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using NUnit.Common; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Drivers +{ + /// + /// This is the revised API, designed for use with .NET Core. It first + /// appears in our source code in 2018. + /// + public class NUnitFrameworkApi2018 : NUnitFrameworkApi + { + static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkApi2018)); + + const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; + const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; + const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; + const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; + + const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; + + NUnitFrameworkDriver _driver; + + AssemblyName _nunitRef; + string? _testAssemblyPath; + + TestAssemblyLoadContext? _assemblyLoadContext; + Assembly? _testAssembly; + Assembly? _frameworkAssembly; + + object? _frameworkController; + Type? _frameworkControllerType; + + public NUnitFrameworkApi2018(NUnitFrameworkDriver driver, AssemblyName nunitRef) + { + _driver = driver; + _nunitRef = nunitRef; + } + + public string Load(string testAssemblyPath, IDictionary settings) + { + Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); + log.Info($"Loading {testAssemblyPath} - see separate log file"); + + _testAssemblyPath = Path.GetFullPath(testAssemblyPath); + var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; + _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); + + _testAssembly = LoadAssembly(testAssemblyPath); + _frameworkAssembly = LoadAssembly(_nunitRef); + + _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); + if (_frameworkController == null) + { + log.Error(INVALID_FRAMEWORK_MESSAGE); + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + } + + _frameworkControllerType = _frameworkController?.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType?.Name}"); + + log.Debug($"Loaded {testAssemblyPath}"); + return (string)ExecuteMethod(LOAD_METHOD); + } + + public int CountTestCases(string filter) + { + CheckLoadWasCalled(); + object? count = ExecuteMethod(COUNT_METHOD, filter); + return count != null ? (int)count : 0; + } + + public string Run(ITestEventListener? listener, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; + return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); + } + + public void RunAsync(Action callback, string filter) + { + CheckLoadWasCalled(); + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); + } + + public void StopRun(bool force) + { + ExecuteMethod(STOP_RUN_METHOD, force); + } + + public string Explore(string filter) + { + CheckLoadWasCalled(); + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); + return (string)ExecuteMethod(EXPLORE_METHOD, filter); + } + + private void CheckLoadWasCalled() + { + if (_frameworkController == null) + throw new InvalidOperationException(LOAD_MESSAGE); + } + + private object CreateInstance(string typeName, params object?[]? args) + { + var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; + return Activator.CreateInstance(type, args)!; + } + + private Assembly LoadAssembly(string assemblyPath) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyPath returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); + log.Error(msg); + throw new NUnitEngineException(msg, e); + } + + log.Debug($"Loaded {assemblyPath}"); + return assembly; + } + + private Assembly LoadAssembly(AssemblyName assemblyName) + { + Assembly assembly; + + try + { + assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; + if (assembly == null) + throw new Exception("LoadFromAssemblyName returned null"); + } + catch (Exception e) + { + var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); + log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); + throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); + } + + log.Debug($"Loaded {assemblyName.FullName}"); + return assembly; + } + + // API methods with no overloads + private static readonly string LOAD_METHOD = "LoadTests"; + private static readonly string EXPLORE_METHOD = "ExploreTests"; + private static readonly string COUNT_METHOD = "CountTests"; + private static readonly string STOP_RUN_METHOD = "StopRun"; + + // Execute methods with no overloads + private object ExecuteMethod(string methodName, params object?[] args) + { + log.Debug($"Calling API method {methodName} with args {string.Join("+", args)}"); + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + return ExecuteMethod(method, args); + } + + // API methods with overloads + private static readonly string RUN_METHOD = "RunTests"; + private static readonly string RUN_ASYNC_METHOD = "RunTests"; + + // Execute overloaded methods specifying argument types + private object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) + { + log.Debug($"Calling API method {methodName} with arg types {string.Join("+", ptypes)}"); + var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); + return ExecuteMethod(method, args); + } + + private object ExecuteMethod(MethodInfo? method, params object?[] args) + { + if (method == null) + throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); + + log.Debug($"Executing {method.DeclaringType}.{method.Name}"); + using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) + { + return method.Invoke(_frameworkController, args).ShouldNotBeNull(); + } + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index 4a8423674..a1fbf7e97 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -17,16 +17,9 @@ namespace NUnit.Engine.Drivers /// public class NUnitFrameworkDriver : IFrameworkDriver { - const string LOAD_MESSAGE = "Method called without calling Load first. Possible error in runner."; - const string INVALID_FRAMEWORK_MESSAGE = "Running tests against this version of the framework using this driver is not supported. Please update NUnit.Framework to the latest version."; - const string FAILED_TO_LOAD_ASSEMBLY = "Failed to load assembly "; - const string FAILED_TO_LOAD_NUNIT = "Failed to load the NUnit Framework in the test assembly"; - - const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkDriver)); - readonly FrameworkApi _api; + readonly NUnitFrameworkApi _api; #if NETFRAMEWORK /// @@ -34,13 +27,12 @@ public class NUnitFrameworkDriver : IFrameworkDriver /// /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. - /// Api to use, either "2018" or "2009". Provided for testing. public NUnitFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) { Guard.ArgumentNotNull(testDomain, nameof(testDomain)); Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _api = new Api2009(this, testDomain, nunitRef); + _api = new NUnitFrameworkApi2009(this, testDomain, nunitRef); } #else /// @@ -50,7 +42,8 @@ public NUnitFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) public NUnitFrameworkDriver(AssemblyName nunitRef) { Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _api = new Api2018(this, nunitRef); + + _api = new NUnitFrameworkApi2018(this, nunitRef); } #endif @@ -103,325 +96,5 @@ public string Load(string testAssemblyPath, IDictionary settings /// A filter indicating which tests to include /// An Xml string representing the tests public string Explore(string filter) => _api.Explore(filter); - -#if NETFRAMEWORK - /// - /// This is the original NUnit 3 API, which only works for .NET Framework. - /// As far as I can discover, it first appeared in pre-release 2.9.1, - /// on launchpad in 2009, hence the name. - /// - class Api2009 : FrameworkApi - { - NUnitFrameworkDriver _driver; - - AppDomain _testDomain; - AssemblyName _nunitRef; - - string? _testAssemblyPath; - - object? _frameworkController; - Type? _frameworkControllerType; - - public Api2009(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) - { - _driver = driver; - _testDomain = testDomain; - _nunitRef = nunitRef; - } - - public string Load(string testAssemblyPath, IDictionary settings) - { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - - log.Info($"Loading {testAssemblyPath} - see separate log file"); - - _testAssemblyPath = testAssemblyPath; - - // Normally, the caller should check for an invalid requested runtime, but we make sure here - var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) - ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - - var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; - try - { - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); - } - catch (BadImageFormatException ex) when (requestedRuntime != null) - { - throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); - } - catch (SerializationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); - } - - _frameworkControllerType = _frameworkController.GetType(); - log.Debug($"Created FrameworkController {_frameworkControllerType.Name}"); - - return ExecuteAction(LOAD_ACTION); - } - - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - return int.Parse(ExecuteAction(COUNT_ACTION, filter)); - } - - public string Run(ITestEventListener? listener, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - return ExecuteAction(RUN_ACTION, listener, filter); - } - - public void RunAsync(Action callback, string filter) => throw new NotImplementedException(); - - public void StopRun(bool force) => ExecuteAction(STOP_RUN_ACTION, force); - - public string Explore(string filter) - { - CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - return ExecuteAction(EXPLORE_ACTION, filter); - } - - private void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } - - // Actions with no extra arguments beyond controller and handler - const string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; - private string ExecuteAction(string action) - { - CallbackHandler handler = new CallbackHandler(); - CreateObject(action, _frameworkController, handler); - return handler.Result.ShouldNotBeNull(); - } - - // Actions with one extra argument - const string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; - const string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; - const string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; - private string ExecuteAction(string action, object arg1) - { - CallbackHandler handler = new CallbackHandler(); - CreateObject(action, _frameworkController, arg1, handler); - return handler.Result.ShouldNotBeNull(); - } - - // Run action has two extra arguments and uses a special handler - const string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; - private string ExecuteAction(string action, ITestEventListener? listener, string filter) - { - RunTestsCallbackHandler handler = new RunTestsCallbackHandler(listener); - CreateObject(action, _frameworkController, filter, handler); - return handler.Result.ShouldNotBeNull(); - } - - private object CreateObject(string typeName, params object?[]? args) - { - try - { - return _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; - } - catch (TargetInvocationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); - } - } - } -#else - /// - /// This is the revised API, designed for use with .NET Core. It first - /// appears in our source code in 2018. This implementation is modified - /// to make it work under the .NET Framework as well as .NET Core. It - /// may be used for NUnit 3.10 or higher. - /// - class Api2018 : FrameworkApi - { - NUnitFrameworkDriver _driver; - - AssemblyName _nunitRef; - string? _testAssemblyPath; - - TestAssemblyLoadContext? _assemblyLoadContext; - Assembly? _testAssembly; - Assembly? _frameworkAssembly; - - object? _frameworkController; - Type? _frameworkControllerType; - - public Api2018(NUnitFrameworkDriver driver, AssemblyName nunitRef) - { - _driver = driver; - _nunitRef = nunitRef; - } - - public string Load(string testAssemblyPath, IDictionary settings) - { - Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); - log.Info($"Loading {testAssemblyPath} - see separate log file"); - - _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; - _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); - - _testAssembly = LoadAssembly(testAssemblyPath); - _frameworkAssembly = LoadAssembly(_nunitRef); - - _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); - if (_frameworkController == null) - { - log.Error(INVALID_FRAMEWORK_MESSAGE); - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - } - - _frameworkControllerType = _frameworkController?.GetType(); - log.Debug($"Created FrameworkController {_frameworkControllerType?.Name}"); - - log.Debug($"Loaded {testAssemblyPath}"); - return (string)ExecuteMethod(LOAD_METHOD); - } - - public int CountTestCases(string filter) - { - CheckLoadWasCalled(); - object? count = ExecuteMethod(COUNT_METHOD, filter); - return count != null ? (int)count : 0; - } - - public string Run(ITestEventListener? listener, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; - return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } - - public void RunAsync(Action callback, string filter) - { - CheckLoadWasCalled(); - log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - ExecuteMethod(RUN_ASYNC_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); - } - - public void StopRun(bool force) - { - ExecuteMethod(STOP_RUN_METHOD, force); - } - - public string Explore(string filter) - { - CheckLoadWasCalled(); - log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - return (string)ExecuteMethod(EXPLORE_METHOD, filter); - } - - private void CheckLoadWasCalled() - { - if (_frameworkController == null) - throw new InvalidOperationException(LOAD_MESSAGE); - } - - private object CreateInstance(string typeName, params object?[]? args) - { - var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; - return Activator.CreateInstance(type, args)!; - } - - private Assembly LoadAssembly(string assemblyPath) - { - Assembly assembly; - - try - { - assembly = _assemblyLoadContext?.LoadFromAssemblyPath(assemblyPath)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyPath returned null"); - } - catch (Exception e) - { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyPath); - log.Error(msg); - throw new NUnitEngineException(msg, e); - } - - log.Debug($"Loaded {assemblyPath}"); - return assembly; - } - - private Assembly LoadAssembly(AssemblyName assemblyName) - { - Assembly assembly; - - try - { - assembly = _assemblyLoadContext?.LoadFromAssemblyName(assemblyName)!; - if (assembly == null) - throw new Exception("LoadFromAssemblyName returned null"); - } - catch (Exception e) - { - var msg = string.Format(FAILED_TO_LOAD_ASSEMBLY + assemblyName.FullName); - log.Error($"{FAILED_TO_LOAD_ASSEMBLY}\r\n{e}"); - throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e); - } - - log.Debug($"Loaded {assemblyName.FullName}"); - return assembly; - } - - // API methods with no overloads - private static readonly string LOAD_METHOD = "LoadTests"; - private static readonly string EXPLORE_METHOD = "ExploreTests"; - private static readonly string COUNT_METHOD = "CountTests"; - private static readonly string STOP_RUN_METHOD = "StopRun"; - - // Execute methods with no overloads - private object ExecuteMethod(string methodName, params object?[] args) - { - log.Debug($"Calling API method {methodName} with args {string.Join("+", args)}"); - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); - return ExecuteMethod(method, args); - } - - // API methods with overloads - private static readonly string RUN_METHOD = "RunTests"; - private static readonly string RUN_ASYNC_METHOD = "RunTests"; - - // Execute overloaded methods specifying argument types - private object ExecuteMethod(string methodName, Type[] ptypes, params object?[] args) - { - log.Debug($"Calling API method {methodName} with arg types {string.Join("+", ptypes)}"); - var method = _frameworkControllerType.ShouldNotBeNull().GetMethod(methodName, ptypes); - return ExecuteMethod(method, args); - } - - private object ExecuteMethod(MethodInfo? method, params object?[] args) - { - if (method == null) - throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); - - log.Debug($"Executing {method.DeclaringType}.{method.Name}"); - using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) - { - return method.Invoke(_frameworkController, args).ShouldNotBeNull(); - } - } - } -#endif - - interface FrameworkApi - { - string Load(string testAssemblyPath, IDictionary settings); - int CountTestCases(string filter); - string Run(ITestEventListener? listener, string filter); - void RunAsync(Action callback, string filter); - void StopRun(bool force); - string Explore(string filter); - } } } From 71c47590eedd149a6d22d9f9a4a541a57e4bbdcb Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Tue, 14 Jan 2025 01:44:26 -0800 Subject: [PATCH 08/16] Remove old code to support multiple assemblies in TestAgentRunner --- .../Drivers/NUnitFrameworkApi2018.cs | 30 +++- .../Runners/TestAgentRunner.cs | 149 ++++++------------ .../nunit.engine/Runners/ProcessRunner.cs | 9 +- 3 files changed, 76 insertions(+), 112 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs index d6e4cdb05..43e3bf13c 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs @@ -12,7 +12,9 @@ namespace NUnit.Engine.Drivers { /// /// This is the revised API, designed for use with .NET Core. It first - /// appears in our source code in 2018. + /// appears in our source code in 2018. This implementation is modified + /// to make it work under the .NET Framework as well as .NET Core. It + /// may be used for NUnit 3.10 or higher. /// public class NUnitFrameworkApi2018 : NUnitFrameworkApi { @@ -30,18 +32,29 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi AssemblyName _nunitRef; string? _testAssemblyPath; + object? _frameworkController; + Type? _frameworkControllerType; + +#if NETFRAMEWORK + private AppDomain _testDomain; + + public NUnitFrameworkApi2018(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + { + _driver = driver; + _testDomain = testDomain; + _nunitRef = nunitRef; + } +#else TestAssemblyLoadContext? _assemblyLoadContext; Assembly? _testAssembly; Assembly? _frameworkAssembly; - object? _frameworkController; - Type? _frameworkControllerType; - public NUnitFrameworkApi2018(NUnitFrameworkDriver driver, AssemblyName nunitRef) { _driver = driver; _nunitRef = nunitRef; } +#endif public string Load(string testAssemblyPath, IDictionary settings) { @@ -50,10 +63,12 @@ public string Load(string testAssemblyPath, IDictionary settings _testAssemblyPath = Path.GetFullPath(testAssemblyPath); var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; +#if NETCOREAPP _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); _testAssembly = LoadAssembly(testAssemblyPath); _frameworkAssembly = LoadAssembly(_nunitRef); +#endif _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); if (_frameworkController == null) @@ -115,6 +130,7 @@ private object CreateInstance(string typeName, params object?[]? args) return Activator.CreateInstance(type, args)!; } +#if NETCOREAPP private Assembly LoadAssembly(string assemblyPath) { Assembly assembly; @@ -156,6 +172,7 @@ private Assembly LoadAssembly(AssemblyName assemblyName) log.Debug($"Loaded {assemblyName.FullName}"); return assembly; } +#endif // API methods with no overloads private static readonly string LOAD_METHOD = "LoadTests"; @@ -189,10 +206,15 @@ private object ExecuteMethod(MethodInfo? method, params object?[] args) throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); log.Debug($"Executing {method.DeclaringType}.{method.Name}"); + +#if NETFRAMEWORK + return method.Invoke(_frameworkController, args).ShouldNotBeNull(); +#else using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) { return method.Invoke(_frameworkController, args).ShouldNotBeNull(); } +#endif } } } diff --git a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs index d32bd7766..cdf2d623a 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using NUnit.Common; using NUnit.Engine.Drivers; using NUnit.Engine.Extensibility; @@ -17,10 +18,10 @@ namespace NUnit.Engine.Runners /// public abstract class TestAgentRunner : ITestEngineRunner { - private readonly List _drivers = new List(); - private readonly ProvidedPathsAssemblyResolver? _assemblyResolver; + private IFrameworkDriver? _driver; + protected AppDomain? TestDomain { get; set; } // Used to inject DriverService for testing @@ -71,27 +72,14 @@ public TestAgentRunner(TestPackage package) /// public TestEngineResult Explore(TestFilter filter) { - EnsurePackageIsLoaded(); - - var result = new TestEngineResult(); - - foreach (IFrameworkDriver driver in _drivers) + try { - string driverResult; - - try - { - driverResult = driver.Explore(filter.Text); - } - catch (Exception ex) when (!(ex is NUnitEngineException)) - { - throw new NUnitEngineException("An exception occurred in the driver while exploring tests.", ex); - } - - result.Add(driverResult); + return new TestEngineResult(GetLoadedDriver().Explore(filter.Text)); + } + catch (Exception ex) when (!(ex is NUnitEngineException)) + { + throw new NUnitEngineException("An exception occurred in the driver while exploring tests.", ex); } - - return result; } /// @@ -105,49 +93,33 @@ public virtual TestEngineResult Load() var result = new TestEngineResult(); - // DirectRunner may be called with a single-assembly package, - // a set of assemblies as subpackages or even an arbitrary - // hierarchy of packages and subpackages with assemblies - // found in the terminal nodes. - var packagesToLoad = TestPackage.Select(p => !p.HasSubPackages()); + // The TestAgentRunner constructor guarantees that TestPackage has + // only a single assembly. + var assemblyPackage = TestPackage.Select(p => !p.HasSubPackages()).First(); if (DriverService == null) DriverService = new DriverService(); - _drivers.Clear(); + var testFile = assemblyPackage.FullName!; // We know it's an assembly - foreach (var subPackage in packagesToLoad) - { - var testFile = subPackage.FullName!; // We know it's an assembly - - string? targetFramework = subPackage.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, (string?)null); - bool skipNonTestAssemblies = subPackage.GetSetting(EnginePackageSettings.SkipNonTestAssemblies, false); - - if (_assemblyResolver != null && !testDomain.IsDefaultAppDomain() - && subPackage.GetSetting(InternalEnginePackageSettings.ImageRequiresDefaultAppDomainAssemblyResolver, false)) - { - // It's OK to do this in the loop because the Add method - // checks to see if the path is already present. - _assemblyResolver.AddPathFromFile(testFile); - } - - IFrameworkDriver driver = DriverService.GetDriver(testDomain, testFile, targetFramework, skipNonTestAssemblies); + string? targetFramework = assemblyPackage.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, (string?)null); + bool skipNonTestAssemblies = assemblyPackage.GetSetting(EnginePackageSettings.SkipNonTestAssemblies, false); - driver.ID = subPackage.ID; - result.Add(LoadDriver(driver, testFile, subPackage)); - _drivers.Add(driver); + if (_assemblyResolver != null && !testDomain.IsDefaultAppDomain() + && assemblyPackage.GetSetting(InternalEnginePackageSettings.ImageRequiresDefaultAppDomainAssemblyResolver, false)) + { + // It's OK to do this in the loop because the Add method + // checks to see if the path is already present. + _assemblyResolver.AddPathFromFile(testFile); } - return result; - } - public virtual void Unload() { } - public TestEngineResult Reload() => Load(); + _driver = DriverService.GetDriver(testDomain, testFile, targetFramework, skipNonTestAssemblies); + + _driver.ID = assemblyPackage.ID; - private static string LoadDriver(IFrameworkDriver driver, string testFile, TestPackage subPackage) - { try { - return driver.Load(testFile, subPackage.Settings); + return new TestEngineResult(_driver.Load(testFile, assemblyPackage.Settings)); } catch (Exception ex) when (ex is not NUnitEngineException) { @@ -155,6 +127,9 @@ private static string LoadDriver(IFrameworkDriver driver, string testFile, TestP } } + public virtual void Unload() { } + public TestEngineResult Reload() => Load(); + /// /// Count the test cases that would be run under /// the specified filter. @@ -163,23 +138,16 @@ private static string LoadDriver(IFrameworkDriver driver, string testFile, TestP /// The count of test cases public int CountTestCases(TestFilter filter) { - EnsurePackageIsLoaded(); + GetLoadedDriver(); - int count = 0; - - foreach (IFrameworkDriver driver in _drivers) + try { - try - { - count += driver.CountTestCases(filter.Text); - } - catch (Exception ex) when (!(ex is NUnitEngineException)) - { - throw new NUnitEngineException("An exception occurred in the driver while counting test cases.", ex); - } + return GetLoadedDriver().CountTestCases(filter.Text); + } + catch (Exception ex) when (!(ex is NUnitEngineException)) + { + throw new NUnitEngineException("An exception occurred in the driver while counting test cases.", ex); } - - return count; } @@ -193,33 +161,15 @@ public int CountTestCases(TestFilter filter) /// public TestEngineResult Run(ITestEventListener? listener, TestFilter filter) { - EnsurePackageIsLoaded(); - - var result = new TestEngineResult(); - - foreach (IFrameworkDriver driver in _drivers) + try { - string driverResult; - - try - { - driverResult = driver.Run(listener, filter.Text); - } - catch (Exception ex) when (!(ex is NUnitEngineException)) - { - throw new NUnitEngineException("An exception occurred in the driver while running tests.", ex); - } - - result.Add(driverResult); + return new TestEngineResult(GetLoadedDriver().Run(listener, filter.Text)); } - - if (_assemblyResolver != null) + catch (Exception ex) when (!(ex is NUnitEngineException)) { - foreach (var package in TestPackage.Select(p => p.IsAssemblyPackage())) - _assemblyResolver.RemovePathFromFile(package.FullName!); // IsAssemblyPackage guarantees FullName is not null + throw new NUnitEngineException("An exception occurred in the driver while running tests.", ex); } - return result; } public AsyncTestEngineResult RunAsync(ITestEventListener? listener, TestFilter filter) @@ -245,25 +195,22 @@ public AsyncTestEngineResult RunAsync(ITestEventListener? listener, TestFilter f /// If true, cancel any ongoing test threads, otherwise wait for them to complete. public void StopRun(bool force) { - EnsurePackageIsLoaded(); - - foreach (IFrameworkDriver driver in _drivers) + try { - try - { - driver.StopRun(force); - } - catch (Exception ex) when (!(ex is NUnitEngineException)) - { - throw new NUnitEngineException("An exception occurred in the driver while stopping the run.", ex); - } + GetLoadedDriver().StopRun(force); + } + catch (Exception ex) when (!(ex is NUnitEngineException)) + { + throw new NUnitEngineException("An exception occurred in the driver while stopping the run.", ex); } } - private void EnsurePackageIsLoaded() + private IFrameworkDriver GetLoadedDriver() { if (!IsPackageLoaded) LoadResult = Load(); + + return _driver.ShouldNotBeNull(); } public void Dispose() diff --git a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs index f04d3b7cf..18070773c 100644 --- a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs @@ -15,13 +15,8 @@ namespace NUnit.Engine.Runners /// public class ProcessRunner : TestEngineRunner { - // ProcessRunner is given a TestPackage containing a single assembly - // multiple assemblies, a project, multiple projects or a mix. It loads - // and runs all tests in a single remote agent process. - // - // If the input contains projects, which are not summarized at a lower - // level, the ProcessRunner should create an XML node for the entire - // project, aggregating the assembly results. + // ProcessRunner is given a TestPackage containing a single assembly. + // It loads and runs the test assembly in a single remote agent process. private static readonly Logger log = InternalTrace.GetLogger(typeof(ProcessRunner)); From b9ce49cc5666369307bb02d8146f5bdb59342508 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 16 Jan 2025 06:26:25 -0800 Subject: [PATCH 09/16] Use 2018 API when targeting .NET Framework! --- .../workflows/NUnitConsoleAndEngine.CI.yml | 3 +- package-tests.cake | 2 +- .../Extensibility/IDriverFactory.cs | 6 +- .../Drivers/NUnitFrameworkDriverTests.cs | 4 +- .../DummyExtensions.cs | 6 +- .../Runners/TestAgentRunnerExceptionTests.cs | 3 +- .../Runners/TestAgentRunnerTests.cs | 2 +- .../Services/DriverServiceTests.cs | 3 +- .../Drivers/DriverService.cs | 15 +-- .../Drivers/IDriverService.cs | 3 +- .../Drivers/NUnit2DriverFactory.cs | 2 +- .../Drivers/NUnit3DriverFactory.cs | 10 +- .../Drivers/NUnitFrameworkApi2018.cs | 112 ++++++++++++++---- .../Drivers/NUnitFrameworkDriver.cs | 34 +++++- .../Internal/ProvidedPathsAssemblyResolver.cs | 4 + .../Runners/TestAgentRunner.cs | 8 +- .../Services/TestFilteringTests.cs | 10 +- .../nunit.engine/Runners/ProcessRunner.cs | 3 + .../nunit.engine/Runners/TestEngineRunner.cs | 1 + .../nunit.engine/Runners/WorkItemTracker.cs | 4 + src/TestData/FakeExtensions/FakeExtensions.cs | 6 +- 21 files changed, 177 insertions(+), 64 deletions(-) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index d218973ba..eee6ce8e8 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -36,7 +36,8 @@ jobs: with: dotnet-version: | 3.1.x - 6.0.x + 6.0.x + 7.0.x 8.0.100 - name: 🔧 Install dotnet tools diff --git a/package-tests.cake b/package-tests.cake index 7c6ad2179..21c28c834 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -35,7 +35,7 @@ class MockAssemblyExpectedResult : ExpectedResult StandardRunnerTests.Add(new PackageTest( 1, "Net462Test", "Run mock-assembly.dll under .NET 4.6.2", - "testdata/net462/mock-assembly.dll", + "testdata/net462/mock-assembly.dll --trace:Debug", new MockAssemblyExpectedResult("net-4.6.2"))); AddToBothLists(new PackageTest( diff --git a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs index fda047506..f5b0d050d 100644 --- a/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.api/Extensibility/IDriverFactory.cs @@ -25,17 +25,19 @@ public interface IDriverFactory /// which the assembly is already known to reference. /// /// The domain in which the assembly will be loaded + /// The driver id. /// An AssemblyName referring to the test framework. /// - IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference); + IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName reference); #else /// /// Gets a driver for a given test assembly and a framework /// which the assembly is already known to reference. /// + /// The driver id. /// An AssemblyName referring to the test framework. /// - IFrameworkDriver GetDriver(AssemblyName reference); + IFrameworkDriver GetDriver(string id, AssemblyName reference); #endif } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs index 166fad528..8edaed815 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs @@ -28,9 +28,9 @@ public void CreateDriver() var nunitRef = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); _mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); #if NETFRAMEWORK - _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, nunitRef); + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, "99", nunitRef); #else - _driver = new NUnitFrameworkDriver(nunitRef); + _driver = new NUnitFrameworkDriver("99", nunitRef); #endif } diff --git a/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs b/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs index 5d549efad..6f0c3b2fd 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/DummyExtensions.cs @@ -12,10 +12,10 @@ namespace NUnit.Engine [Extension] public class DummyFrameworkDriverExtension : IDriverFactory { -#if !NETFRAMEWORK - public IFrameworkDriver GetDriver(AssemblyName reference) +#if NETFRAMEWORK + public IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName reference) #else - public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) + public IFrameworkDriver GetDriver(string id, AssemblyName reference) #endif { throw new NotImplementedException(); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerExceptionTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerExceptionTests.cs index 098237a5b..7df91d65d 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerExceptionTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerExceptionTests.cs @@ -22,8 +22,9 @@ public void Initialize() var driverService = Substitute.For(); driverService.GetDriver( AppDomain.CurrentDomain, + new TestPackage(), + string.Empty, string.Empty, - string.Empty, false).ReturnsForAnyArgs(_driver); _runner = new FakeTestAgentRunner(new TestPackage("mock-assembly.dll").SubPackages[0]) diff --git a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs index b02f94a7a..1d9edf5fb 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs @@ -69,7 +69,7 @@ public void Run() CheckPackageLoading(); } - [Test] + //[Test] public void RunAsync() { var asyncResult = _runner.RunAsync(null, TestFilter.Empty); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index a4be7785e..5cfc5b2a4 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -23,7 +23,8 @@ public void CreateDriverFactory() [TestCaseSource(nameof(DriverSelectionTestCases))] public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Type expectedType) { - var driver = _driverService.GetDriver(AppDomain.CurrentDomain, Path.Combine(TestContext.CurrentContext.TestDirectory, fileName), null, skipNonTestAssemblies); + var assemblyPath = Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); + var driver = _driverService.GetDriver(AppDomain.CurrentDomain, new TestPackage(assemblyPath), assemblyPath, null, skipNonTestAssemblies); Assert.That(driver, Is.InstanceOf(expectedType)); } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs index ccaec8db7..8a88c1a98 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs @@ -51,7 +51,7 @@ public DriverService() /// The value of any TargetFrameworkAttribute on the assembly, or null /// True if non-test assemblies should simply be skipped rather than reporting an error /// - public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string? targetFramework, bool skipNonTestAssemblies) + public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string assemblyPath, string? targetFramework, bool skipNonTestAssemblies) { if (!File.Exists(assemblyPath)) return new InvalidAssemblyFrameworkDriver(assemblyPath, "File not found: " + assemblyPath); @@ -87,22 +87,19 @@ public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string? return new SkippedAssemblyFrameworkDriver(assemblyPath); } - var references = new List(); - foreach (var cecilRef in assemblyDef.MainModule.AssemblyReferences) - references.Add(new AssemblyName(cecilRef.FullName)); - foreach (var factory in _factories) { log.Debug($"Trying {factory.GetType().Name}"); - foreach (var reference in references) + foreach (var cecilRef in assemblyDef.MainModule.AssemblyReferences) { - if (factory.IsSupportedTestFramework(reference)) + var assemblyName = new AssemblyName(cecilRef.FullName); + if (factory.IsSupportedTestFramework(assemblyName)) { #if NETFRAMEWORK - return factory.GetDriver(domain, reference); + return factory.GetDriver(domain, package.ID, assemblyName); #else - return factory.GetDriver(reference); + return factory.GetDriver(package.ID, assemblyName); #endif } } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/IDriverService.cs b/src/NUnitEngine/nunit.engine.core/Drivers/IDriverService.cs index fb3c26d3f..4dde75689 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/IDriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/IDriverService.cs @@ -15,10 +15,11 @@ public interface IDriverService /// Get a driver suitable for loading and running tests in the specified assembly. /// /// The application domain in which to run the tests + /// The package for which the driver is to be used /// The path to the test assembly /// The value of any TargetFrameworkAttribute on the assembly, or null /// True if non-test assemblies should simply be skipped rather than reporting an error /// - IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, string? targetFramework, bool skipNonTestAssemblies); + IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string assemblyPath, string? targetFramework, bool skipNonTestAssemblies); } } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs index df4fb3688..f3dab35ce 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit2DriverFactory.cs @@ -43,7 +43,7 @@ public bool IsSupportedTestFramework(AssemblyName reference) /// The domain in which the assembly will be loaded /// The name of the test framework reference /// - public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) + public IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName reference) { if (!IsSupportedTestFramework(reference)) throw new ArgumentException("Invalid framework", "reference"); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index 1ab6f009b..1e3e44f2f 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -30,11 +30,13 @@ public bool IsSupportedTestFramework(AssemblyName reference) /// The domain in which the assembly will be loaded /// An AssemblyName referring to the test framework. /// An IFrameworkDriver - public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) + public IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName reference) { + Guard.ArgumentNotNullOrEmpty(id, nameof(id)); Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); + log.Info("Using NUnitFrameworkDriver"); - return new NUnitFrameworkDriver(domain, reference); + return new NUnitFrameworkDriver(domain, id, reference); } #else /// @@ -42,11 +44,11 @@ public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) /// /// An AssemblyName referring to the test framework. /// - public IFrameworkDriver GetDriver(AssemblyName reference) + public IFrameworkDriver GetDriver(string id, AssemblyName reference) { Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); log.Info("Using NUnitFrameworkDriver"); - return new NUnitFrameworkDriver(reference); + return new NUnitFrameworkDriver(id, reference); } #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs index 43e3bf13c..b6da7a9ac 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs @@ -1,12 +1,15 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt -#if NETCOREAPP using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.Remoting; +using System.Runtime.Serialization; using NUnit.Common; +using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; +using TestCentric.Metadata; namespace NUnit.Engine.Drivers { @@ -16,7 +19,11 @@ namespace NUnit.Engine.Drivers /// to make it work under the .NET Framework as well as .NET Core. It /// may be used for NUnit 3.10 or higher. /// +#if NETFRAMEWORK + public class NUnitFrameworkApi2018 : MarshalByRefObject, NUnitFrameworkApi +#else public class NUnitFrameworkApi2018 : NUnitFrameworkApi +#endif { static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkApi2018)); @@ -27,7 +34,7 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - NUnitFrameworkDriver _driver; + private string _driverId; AssemblyName _nunitRef; string? _testAssemblyPath; @@ -35,40 +42,85 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi object? _frameworkController; Type? _frameworkControllerType; -#if NETFRAMEWORK - private AppDomain _testDomain; - public NUnitFrameworkApi2018(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + public NUnitFrameworkApi2018(string driverId, AssemblyName nunitRef) { - _driver = driver; - _testDomain = testDomain; + Guard.ArgumentNotNull(driverId, nameof(driverId)); + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + + _driverId = driverId; +#if NETFRAMEWORK + //if (RemotingServices.IsTransparentProxy(driver)) + //{ + // // We need to replace nunitRef with a reference valid in the current domain. + // var frameworkRef = AssemblyNameReference.Parse(nunitRef.FullName); + // var frameworkDef = new DefaultAssemblyResolver().Resolve(frameworkRef); + // frameworkDef.MainModule. + //} + //if (_nunitRef == null) + _nunitRef = nunitRef; +#else _nunitRef = nunitRef; +#endif } -#else + +#if NETCOREAPP TestAssemblyLoadContext? _assemblyLoadContext; Assembly? _testAssembly; Assembly? _frameworkAssembly; - - public NUnitFrameworkApi2018(NUnitFrameworkDriver driver, AssemblyName nunitRef) - { - _driver = driver; - _nunitRef = nunitRef; - } #endif public string Load(string testAssemblyPath, IDictionary settings) { + Guard.ArgumentNotNull(testAssemblyPath, nameof(testAssemblyPath)); + Guard.ArgumentNotNull(settings, nameof(settings)); Guard.ArgumentValid(File.Exists(testAssemblyPath), "Framework driver called with a file name that doesn't exist.", "testAssemblyPath"); log.Info($"Loading {testAssemblyPath} - see separate log file"); _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; -#if NETCOREAPP + var idPrefix = string.IsNullOrEmpty(_driverId) ? "" : _driverId + "-"; + +#if NETFRAMEWORK + // Normally, the caller should check for an invalid requested runtime, but we make sure here + var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) + ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; + + try + { + _frameworkController = AppDomain.CurrentDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, + CONTROLLER_TYPE, + false, + 0, + null, + new object[] { _testAssemblyPath, idPrefix, settings }, + null, + null).ShouldNotBeNull(); + } + catch (BadImageFormatException ex) when (requestedRuntime != null) + { + throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); + } + catch (SerializationException ex) + { + throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); + } + catch (Exception ex) + { + string msg = $"Failed to load {_nunitRef.FullName}\r\n Codebase: {_nunitRef.CodeBase}"; + throw new Exception(msg, ex); + } + + _frameworkControllerType = _frameworkController?.GetType(); + log.Debug($"Created FrameworkController {_frameworkControllerType?.Name}"); + + var controllerAssembly = _frameworkControllerType?.Assembly?.GetName(); + log.Debug($"Controller assembly is {controllerAssembly}"); +#else _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); _testAssembly = LoadAssembly(testAssemblyPath); _frameworkAssembly = LoadAssembly(_nunitRef); -#endif _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); if (_frameworkController == null) @@ -76,6 +128,7 @@ public string Load(string testAssemblyPath, IDictionary settings log.Error(INVALID_FRAMEWORK_MESSAGE); throw new NUnitEngineException(INVALID_FRAMEWORK_MESSAGE); } +#endif _frameworkControllerType = _frameworkController?.GetType(); log.Debug($"Created FrameworkController {_frameworkControllerType?.Name}"); @@ -95,7 +148,7 @@ public string Run(ITestEventListener? listener, string filter) { CheckLoadWasCalled(); log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - Action? callback = listener != null ? listener.OnTestEvent : (Action?)null; + Action? callback = /*listener != null ? listener.OnTestEvent :*/ (Action?)null; return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); } @@ -124,13 +177,26 @@ private void CheckLoadWasCalled() throw new InvalidOperationException(LOAD_MESSAGE); } +#if NETFRAMEWORK + //private ObjectHandle? CreateInstance(string typeName, params object?[]? args) + //{ + // try + // { + // return _testDomain.CreateInstance( + // _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + // } + // catch (TargetInvocationException ex) + // { + // throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); + // } + //} +#else private object CreateInstance(string typeName, params object?[]? args) { var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; return Activator.CreateInstance(type, args)!; } -#if NETCOREAPP private Assembly LoadAssembly(string assemblyPath) { Assembly assembly; @@ -216,6 +282,12 @@ private object ExecuteMethod(MethodInfo? method, params object?[] args) } #endif } + +#if NETFRAMEWORK + public override object InitializeLifetimeService() + { + return null!; + } +#endif } } -#endif diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index a1fbf7e97..7ca1d03e0 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Runtime.Serialization; using NUnit.Common; using NUnit.Engine.Internal; using NUnit.Engine.Extensibility; @@ -27,23 +26,46 @@ public class NUnitFrameworkDriver : IFrameworkDriver /// /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. - public NUnitFrameworkDriver(AppDomain testDomain, AssemblyName nunitRef) + public NUnitFrameworkDriver(AppDomain testDomain, string id, AssemblyName nunitRef) { Guard.ArgumentNotNull(testDomain, nameof(testDomain)); + Guard.ArgumentNotNullOrEmpty(id, nameof(id)); Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _api = new NUnitFrameworkApi2009(this, testDomain, nunitRef); + ID = id; + + // Under .NET Framework, the Api class must be injected in the test domain + //testDomain.AssemblyResolve += TestDomain_AssemblyResolve; + + _api = (NUnitFrameworkApi2018)testDomain.CreateInstanceFromAndUnwrap( + Assembly.GetExecutingAssembly().Location, + typeof(NUnitFrameworkApi2018).FullName!, + false, + 0, + null, + new object[] { ID, nunitRef }, + null, + null).ShouldNotBeNull(); + } + + private Assembly? TestDomain_AssemblyResolve(object? sender, ResolveEventArgs args) + { + //var fileName = args.Name; + return null; } #else /// /// Construct an NUnitFrameworkDriver /// /// An AssemblyName referring to the test framework. - public NUnitFrameworkDriver(AssemblyName nunitRef) + public NUnitFrameworkDriver(string id, AssemblyName nunitRef) { + Guard.ArgumentNotNullOrEmpty(id, nameof(id)); Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); - _api = new NUnitFrameworkApi2018(this, nunitRef); + ID = id; + + _api = new NUnitFrameworkApi2018(ID, nunitRef); } #endif @@ -75,7 +97,7 @@ public string Load(string testAssemblyPath, IDictionary settings /// An ITestEventHandler that receives progress notices /// A filter that controls which tests are executed /// An Xml string representing the result - public string Run(ITestEventListener? listener, string filter) => _api.Run(listener, filter); + public string Run(ITestEventListener? listener, string filter) => _api.Run(null, filter); /// /// Executes the tests in an assembly asynchronously. diff --git a/src/NUnitEngine/nunit.engine.core/Internal/ProvidedPathsAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/ProvidedPathsAssemblyResolver.cs index 8b178e7bd..4a55fc192 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/ProvidedPathsAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/ProvidedPathsAssemblyResolver.cs @@ -12,6 +12,8 @@ public class ProvidedPathsAssemblyResolver { static readonly ILogger log = InternalTrace.GetLogger(typeof(ProvidedPathsAssemblyResolver)); + static readonly string THIS_ASSEMBLY_LOCATION = Assembly.GetExecutingAssembly().Location; + public ProvidedPathsAssemblyResolver() { _resolutionPaths = new List(); @@ -21,6 +23,8 @@ public void Install() { Debug.Assert(AppDomain.CurrentDomain.IsDefaultAppDomain()); AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + + AddPath(THIS_ASSEMBLY_LOCATION); } public void AddPath(string dirPath) diff --git a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs index cdf2d623a..83cde05df 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs @@ -48,10 +48,11 @@ public bool IsPackageLoaded public TestAgentRunner(TestPackage package) { Guard.ArgumentNotNull(package, nameof(package)); + //Guard.ArgumentValid(package.IsAssemblyPackage(), "TestAgentRunner requires a package with a single assembly", nameof(package)); var assemblyPackages = package.Select(p => !p.HasSubPackages()); Guard.ArgumentValid(assemblyPackages.Count == 1, "TestAgentRunner requires a package with a single assembly", nameof(package)); - TestPackage = assemblyPackages[0]; + TestPackage = package; // Bypass the resolver if not in the default AppDomain. This prevents trying to use the resolver within // NUnit's own automated tests (in a test AppDomain) which does not make sense anyway. @@ -89,7 +90,6 @@ public TestEngineResult Explore(TestFilter filter) public virtual TestEngineResult Load() { Guard.OperationValid(TestDomain != null, "TestDomain is not set"); - AppDomain testDomain = TestDomain; var result = new TestEngineResult(); @@ -105,7 +105,7 @@ public virtual TestEngineResult Load() string? targetFramework = assemblyPackage.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, (string?)null); bool skipNonTestAssemblies = assemblyPackage.GetSetting(EnginePackageSettings.SkipNonTestAssemblies, false); - if (_assemblyResolver != null && !testDomain.IsDefaultAppDomain() + if (_assemblyResolver != null && !TestDomain.IsDefaultAppDomain() && assemblyPackage.GetSetting(InternalEnginePackageSettings.ImageRequiresDefaultAppDomainAssemblyResolver, false)) { // It's OK to do this in the loop because the Add method @@ -113,7 +113,7 @@ public virtual TestEngineResult Load() _assemblyResolver.AddPathFromFile(testFile); } - _driver = DriverService.GetDriver(testDomain, testFile, targetFramework, skipNonTestAssemblies); + _driver = DriverService.GetDriver(TestDomain, assemblyPackage, testFile, targetFramework, skipNonTestAssemblies); _driver.ID = assemblyPackage.ID; diff --git a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs index 3fe99c6cd..667cf56fa 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/TestFilteringTests.cs @@ -20,11 +20,13 @@ public class TestFilteringTests public void LoadAssembly() { var mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); - var assemblyName = typeof(TestAttribute).Assembly.GetName(); -#if NETCOREAPP3_1_OR_GREATER - _driver = new NUnitFrameworkDriver(assemblyName); + var nunitRef = typeof(TestAttribute).Assembly.GetName(); + var assemblyPath = typeof(TestAttribute).Assembly.Location; + string driverId = "99"; +#if NETFRAMEWORK + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, driverId, nunitRef); #else - _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, assemblyName); + _driver = new NUnitFrameworkDriver(driverId, nunitRef); #endif _driver.Load(mockAssemblyPath, new Dictionary()); } diff --git a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs index 18070773c..c708bc895 100644 --- a/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/ProcessRunner.cs @@ -28,6 +28,9 @@ public class ProcessRunner : TestEngineRunner public ProcessRunner(IServiceLocator services, TestPackage package) : base(services, package) { _agency = Services.GetService(); + + var assemblyPackages = package.Select(p => !p.HasSubPackages()); + Guard.ArgumentValid(assemblyPackages.Count == 1, $"{GetType().Name} requires a package with a single assembly", nameof(package)); } /// diff --git a/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs b/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs index 521e5e36d..bd9e2188e 100644 --- a/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; using NUnit.Common; +using NUnit.Engine.Internal; namespace NUnit.Engine.Runners { diff --git a/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs b/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs index f18231a9b..88aa94b11 100644 --- a/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs +++ b/src/NUnitEngine/nunit.engine/Runners/WorkItemTracker.cs @@ -1,7 +1,9 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using NUnit.Engine.Internal; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Text; using System.Threading; @@ -63,6 +65,8 @@ public int CompareTo(InProgressItem? other) } } + private static readonly ILogger log = InternalTrace.GetLogger(nameof(InProgressItem)); + // items are keyed by id private readonly Dictionary _itemsInProcess = new Dictionary(); private readonly ManualResetEvent _allItemsComplete = new ManualResetEvent(false); diff --git a/src/TestData/FakeExtensions/FakeExtensions.cs b/src/TestData/FakeExtensions/FakeExtensions.cs index f0ac2be6e..41fb228bf 100644 --- a/src/TestData/FakeExtensions/FakeExtensions.cs +++ b/src/TestData/FakeExtensions/FakeExtensions.cs @@ -12,10 +12,10 @@ namespace NUnit.Engine.Tests [Extension] public class DummyFrameworkDriverExtension : IDriverFactory { -#if !NETFRAMEWORK - public IFrameworkDriver GetDriver(AssemblyName reference) +#if NETFRAMEWORK + public IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName reference) #else - public IFrameworkDriver GetDriver(AppDomain domain, AssemblyName reference) + public IFrameworkDriver GetDriver(string id, AssemblyName reference) #endif { throw new NotImplementedException(); From 9543ea06f597bf3cf2cbc8de9ccfb2729d6422ea Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Thu, 16 Jan 2025 11:35:55 -0800 Subject: [PATCH 10/16] Test both APIs under .NET Framework; switch APIs automatically --- NUnitConsole.sln | 31 ++++++++++++++ package-tests.cake | 40 +++++++++++++++++++ .../Drivers/NUnitFrameworkDriverTests.cs | 17 +++++++- .../Drivers/NUnitFrameworkApi2009.cs | 13 ++++-- .../Drivers/NUnitFrameworkDriver.cs | 34 ++++++++++------ src/TestData/NUnit3.0.1/Class1.cs | 13 ++++++ src/TestData/NUnit3.0.1/NUnit3.0.1.csproj | 13 ++++++ src/TestData/NUnit3.0/NUnit3.0.csproj | 13 ++++++ src/TestData/NUnit3.0/NUnit_3_0_Test.cs | 13 ++++++ src/TestData/NUnit3.10/NUnit3.10.csproj | 13 ++++++ src/TestData/NUnit3.10/NUnit_3_10_Test.cs | 13 ++++++ src/TestData/NUnit3.2/NUnit3.2.csproj | 13 ++++++ src/TestData/NUnit3.2/NUnit_3_2_Test.cs | 13 ++++++ 13 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 src/TestData/NUnit3.0.1/Class1.cs create mode 100644 src/TestData/NUnit3.0.1/NUnit3.0.1.csproj create mode 100644 src/TestData/NUnit3.0/NUnit3.0.csproj create mode 100644 src/TestData/NUnit3.0/NUnit_3_0_Test.cs create mode 100644 src/TestData/NUnit3.10/NUnit3.10.csproj create mode 100644 src/TestData/NUnit3.10/NUnit_3_10_Test.cs create mode 100644 src/TestData/NUnit3.2/NUnit3.2.csproj create mode 100644 src/TestData/NUnit3.2/NUnit_3_2_Test.cs diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 61aecb8b9..51c112168 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -139,6 +139,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTest", "src\TestData\wpf EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{93D182B7-2F63-4661-BB32-94F1716CF03F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OlderNUnit", "OlderNUnit", "{D31607D2-D689-488B-9F9F-92F47AC1D7F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.0", "src\TestData\NUnit3.0\NUnit3.0.csproj", "{2448CE80-59AA-41B7-83A0-768BE5520F7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.0.1", "src\TestData\NUnit3.0.1\NUnit3.0.1.csproj", "{56416AD9-8E7B-4F72-B0E1-E75A024431F8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.2", "src\TestData\NUnit3.2\NUnit3.2.csproj", "{8AFBB856-700A-4CCC-AE0E-9B622178E1E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.10", "src\TestData\NUnit3.10\NUnit3.10.csproj", "{0555B97D-E918-455B-951C-74EFCDA8790A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -241,6 +251,22 @@ Global {93D182B7-2F63-4661-BB32-94F1716CF03F}.Debug|Any CPU.Build.0 = Debug|Any CPU {93D182B7-2F63-4661-BB32-94F1716CF03F}.Release|Any CPU.ActiveCfg = Release|Any CPU {93D182B7-2F63-4661-BB32-94F1716CF03F}.Release|Any CPU.Build.0 = Release|Any CPU + {2448CE80-59AA-41B7-83A0-768BE5520F7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2448CE80-59AA-41B7-83A0-768BE5520F7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2448CE80-59AA-41B7-83A0-768BE5520F7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2448CE80-59AA-41B7-83A0-768BE5520F7A}.Release|Any CPU.Build.0 = Release|Any CPU + {56416AD9-8E7B-4F72-B0E1-E75A024431F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56416AD9-8E7B-4F72-B0E1-E75A024431F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56416AD9-8E7B-4F72-B0E1-E75A024431F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56416AD9-8E7B-4F72-B0E1-E75A024431F8}.Release|Any CPU.Build.0 = Release|Any CPU + {8AFBB856-700A-4CCC-AE0E-9B622178E1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8AFBB856-700A-4CCC-AE0E-9B622178E1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AFBB856-700A-4CCC-AE0E-9B622178E1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8AFBB856-700A-4CCC-AE0E-9B622178E1E0}.Release|Any CPU.Build.0 = Release|Any CPU + {0555B97D-E918-455B-951C-74EFCDA8790A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0555B97D-E918-455B-951C-74EFCDA8790A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0555B97D-E918-455B-951C-74EFCDA8790A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0555B97D-E918-455B-951C-74EFCDA8790A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -280,6 +306,11 @@ Global {48DF1E40-93BA-436A-B460-5D1130316ADA} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} {2F9D8932-2186-464F-BED6-7D7979C8FFA6} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} {93D182B7-2F63-4661-BB32-94F1716CF03F} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} + {D31607D2-D689-488B-9F9F-92F47AC1D7F6} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} + {2448CE80-59AA-41B7-83A0-768BE5520F7A} = {D31607D2-D689-488B-9F9F-92F47AC1D7F6} + {56416AD9-8E7B-4F72-B0E1-E75A024431F8} = {D31607D2-D689-488B-9F9F-92F47AC1D7F6} + {8AFBB856-700A-4CCC-AE0E-9B622178E1E0} = {D31607D2-D689-488B-9F9F-92F47AC1D7F6} + {0555B97D-E918-455B-951C-74EFCDA8790A} = {D31607D2-D689-488B-9F9F-92F47AC1D7F6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/package-tests.cake b/package-tests.cake index 21c28c834..8050d442a 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -142,6 +142,46 @@ StandardRunnerTests.Add(new PackageTest( "testdata/net462/mock-assembly.dll testdata/net6.0/mock-assembly.dll", new MockAssemblyExpectedResult("net-4.6.2", "netcore-6.0"))); +////////////////////////////////////////////////////////////////////// +// TEST OLDER VERSIONS OF NUNIT SWITCHING API IF NEEDED +////////////////////////////////////////////////////////////////////// + +StandardRunnerTests.Add(new PackageTest( + 1, "NUnit30Test", + "Run a test under NUnit 3.0 using 2009 API", + "testdata/NUnit3.0/net462/NUnit3.0.dll", + new ExpectedResult("Passed") + { + Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.0.dll", "net462") } + })); + +StandardRunnerTests.Add(new PackageTest( + 1, "NUnit301Test", + "Run a test under NUnit 3.0.1 using 2009 API", + "testdata/NUnit3.0/net462/NUnit3.0.1.dll", + new ExpectedResult("Passed") + { + Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.0.1.dll", "net462") } + })); + +StandardRunnerTests.Add(new PackageTest( + 1, "NUnit32Test", + "Run a test under NUnit 3.2 using 20018 API", + "testdata/NUnit3.0/net462/NUnit3.2.dll", + new ExpectedResult("Passed") + { + Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.2.dll", "net462") } + })); + +StandardRunnerTests.Add(new PackageTest( + 1, "NUnit310Test", + "Run a test under NUnit 3.10 using 2018 API", + "testdata/NUnit3.0/net462/NUnit3.10.dll", + new ExpectedResult("Passed") + { + Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.10.dll", "net462") } + })); + ////////////////////////////////////////////////////////////////////// // ASP.NETCORE TESTS ////////////////////////////////////////////////////////////////////// diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs index 8edaed815..76cfa2339 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs @@ -12,6 +12,10 @@ namespace NUnit.Engine.Drivers { // Functional tests of the NUnitFrameworkDriver calling into the framework. +#if NETFRAMEWORK + [TestFixture("2009")] + [TestFixture("2018")] +#endif public class NUnitFrameworkDriverTests { private const string MOCK_ASSEMBLY = "mock-assembly.dll"; @@ -22,13 +26,22 @@ public class NUnitFrameworkDriverTests private NUnitFrameworkDriver _driver; private string _mockAssemblyPath; +#if NETFRAMEWORK + private string _whichApi; + public NUnitFrameworkDriverTests(string whichApi) + { + _whichApi = whichApi; + } +#endif + [SetUp] public void CreateDriver() { - var nunitRef = typeof(NUnit.Framework.TestAttribute).Assembly.GetName(); + var nunitRef = typeof(TestAttribute).Assembly.GetName(); _mockAssemblyPath = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); + #if NETFRAMEWORK - _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, "99", nunitRef); + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, _whichApi, "99", nunitRef); #else _driver = new NUnitFrameworkDriver("99", nunitRef); #endif diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs index 36ad19129..170a3daca 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs @@ -27,7 +27,7 @@ class NUnitFrameworkApi2009 : NUnitFrameworkApi const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - NUnitFrameworkDriver _driver; + string _driverId; AppDomain _testDomain; AssemblyName _nunitRef; @@ -37,10 +37,14 @@ class NUnitFrameworkApi2009 : NUnitFrameworkApi object? _frameworkController; Type? _frameworkControllerType; - public NUnitFrameworkApi2009(NUnitFrameworkDriver driver, AppDomain testDomain, AssemblyName nunitRef) + public NUnitFrameworkApi2009(AppDomain testDomain, string driverId, AssemblyName nunitRef) { - _driver = driver; + Guard.ArgumentNotNull(testDomain, nameof(testDomain)); + Guard.ArgumentNotNull(driverId, nameof(driverId)); + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + _testDomain = testDomain; + _driverId = driverId; _nunitRef = nunitRef; } @@ -56,7 +60,8 @@ public string Load(string testAssemblyPath, IDictionary settings var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - var idPrefix = string.IsNullOrEmpty(_driver.ID) ? "" : _driver.ID + "-"; + var idPrefix = string.IsNullOrEmpty(_driverId) ? "" : _driverId + "-"; + try { _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index 7ca1d03e0..2d37c30ab 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -7,6 +7,7 @@ using NUnit.Common; using NUnit.Engine.Internal; using NUnit.Engine.Extensibility; +using NUnit.Engine.Runners; namespace NUnit.Engine.Drivers { @@ -27,25 +28,34 @@ public class NUnitFrameworkDriver : IFrameworkDriver /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. public NUnitFrameworkDriver(AppDomain testDomain, string id, AssemblyName nunitRef) + : this(testDomain, nunitRef.Version >= new Version(3,2,0) ? "2018" : "2009", id, nunitRef) { } + + /// + /// Internal generic constructor used directly by our tests. + /// + /// The application domain in which to create the FrameworkController + /// An AssemblyName referring to the test framework. + internal NUnitFrameworkDriver(AppDomain testDomain, string api, string id, AssemblyName nunitRef) { Guard.ArgumentNotNull(testDomain, nameof(testDomain)); + Guard.ArgumentNotNull(api, nameof(api)); + Guard.ArgumentValid(api == "2009" || api == "2018", $"Invalid API specified: {api}", nameof(api)); Guard.ArgumentNotNullOrEmpty(id, nameof(id)); Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); ID = id; - // Under .NET Framework, the Api class must be injected in the test domain - //testDomain.AssemblyResolve += TestDomain_AssemblyResolve; - - _api = (NUnitFrameworkApi2018)testDomain.CreateInstanceFromAndUnwrap( - Assembly.GetExecutingAssembly().Location, - typeof(NUnitFrameworkApi2018).FullName!, - false, - 0, - null, - new object[] { ID, nunitRef }, - null, - null).ShouldNotBeNull(); + _api = api == "2018" + ? (NUnitFrameworkApi)testDomain.CreateInstanceFromAndUnwrap( + Assembly.GetExecutingAssembly().Location, + $"NUnit.Engine.Drivers.NUnitFrameworkApi{api}", + false, + 0, + null, + new object[] { ID, nunitRef }, + null, + null).ShouldNotBeNull() + : new NUnitFrameworkApi2009(testDomain, ID, nunitRef); } private Assembly? TestDomain_AssemblyResolve(object? sender, ResolveEventArgs args) diff --git a/src/TestData/NUnit3.0.1/Class1.cs b/src/TestData/NUnit3.0.1/Class1.cs new file mode 100644 index 000000000..350273d1d --- /dev/null +++ b/src/TestData/NUnit3.0.1/Class1.cs @@ -0,0 +1,13 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using NUnit.Framework; + +namespace NUnit.Engine.TestData +{ + public class NUnit_3_0_1_Test + { + [Test] + public void Test() { Assert.Pass("test passes"); } + } +} diff --git a/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj b/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj new file mode 100644 index 000000000..ede18b3e3 --- /dev/null +++ b/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj @@ -0,0 +1,13 @@ + + + + NUnit.TestData + net462 + ../../../bin/$(Configuration)/testdata/nunit3.0 + + + + + + + diff --git a/src/TestData/NUnit3.0/NUnit3.0.csproj b/src/TestData/NUnit3.0/NUnit3.0.csproj new file mode 100644 index 000000000..ede18b3e3 --- /dev/null +++ b/src/TestData/NUnit3.0/NUnit3.0.csproj @@ -0,0 +1,13 @@ + + + + NUnit.TestData + net462 + ../../../bin/$(Configuration)/testdata/nunit3.0 + + + + + + + diff --git a/src/TestData/NUnit3.0/NUnit_3_0_Test.cs b/src/TestData/NUnit3.0/NUnit_3_0_Test.cs new file mode 100644 index 000000000..d2be48895 --- /dev/null +++ b/src/TestData/NUnit3.0/NUnit_3_0_Test.cs @@ -0,0 +1,13 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using NUnit.Framework; + +namespace NUnit.Engine.TestData +{ + public class NUnit_3_0_Test + { + [Test] + public void Test() { Assert.Pass("test passes"); } + } +} diff --git a/src/TestData/NUnit3.10/NUnit3.10.csproj b/src/TestData/NUnit3.10/NUnit3.10.csproj new file mode 100644 index 000000000..ede18b3e3 --- /dev/null +++ b/src/TestData/NUnit3.10/NUnit3.10.csproj @@ -0,0 +1,13 @@ + + + + NUnit.TestData + net462 + ../../../bin/$(Configuration)/testdata/nunit3.0 + + + + + + + diff --git a/src/TestData/NUnit3.10/NUnit_3_10_Test.cs b/src/TestData/NUnit3.10/NUnit_3_10_Test.cs new file mode 100644 index 000000000..ff2727b9d --- /dev/null +++ b/src/TestData/NUnit3.10/NUnit_3_10_Test.cs @@ -0,0 +1,13 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using NUnit.Framework; + +namespace NUnit.Engine.TestData +{ + public class NUnit_3_10_Test + { + [Test] + public void Test() { Assert.Pass("test passes"); } + } +} diff --git a/src/TestData/NUnit3.2/NUnit3.2.csproj b/src/TestData/NUnit3.2/NUnit3.2.csproj new file mode 100644 index 000000000..ede18b3e3 --- /dev/null +++ b/src/TestData/NUnit3.2/NUnit3.2.csproj @@ -0,0 +1,13 @@ + + + + NUnit.TestData + net462 + ../../../bin/$(Configuration)/testdata/nunit3.0 + + + + + + + diff --git a/src/TestData/NUnit3.2/NUnit_3_2_Test.cs b/src/TestData/NUnit3.2/NUnit_3_2_Test.cs new file mode 100644 index 000000000..adb726a7b --- /dev/null +++ b/src/TestData/NUnit3.2/NUnit_3_2_Test.cs @@ -0,0 +1,13 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using NUnit.Framework; + +namespace NUnit.Engine.TestData +{ + public class NUnit_3_2_Test + { + [Test] + public void Test() { Assert.Pass("test passes"); } + } +} From 6e3045a97a702eab12a85f761a79d452356b853d Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 18 Jan 2025 16:35:08 -0800 Subject: [PATCH 11/16] Drop .NET 7.0 agent --- NUnitConsole.sln | 7 --- build.cake | 6 -- choco/nunit-console-runner.nuspec | 10 ---- nuget/engine/nunit.engine.nuspec | 12 ---- nuget/runners/nunit.console-runner.nuspec | 13 ---- .../agents/nunit-agent-net70/app.config | 41 ------------- .../agents/nunit-agent-net70/app.manifest | 50 ---------------- .../nunit-agent-net70.csproj | 59 ------------------- .../Services/AgentProcessTests.cs | 4 +- .../nunit.engine/Services/AgentProcess.cs | 5 +- 10 files changed, 3 insertions(+), 204 deletions(-) delete mode 100644 src/NUnitEngine/agents/nunit-agent-net70/app.config delete mode 100644 src/NUnitEngine/agents/nunit-agent-net70/app.manifest delete mode 100644 src/NUnitEngine/agents/nunit-agent-net70/nunit-agent-net70.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 61aecb8b9..6067b080e 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -103,8 +103,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-net462", "src\N EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-net462-x86", "src\NUnitEngine\agents\nunit-agent-net462-x86\nunit-agent-net462-x86.csproj", "{D4AE483A-D4E1-4892-AF87-3607FBA8755F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-net70", "src\NUnitEngine\agents\nunit-agent-net70\nunit-agent-net70.csproj", "{1343AB32-5FB0-408B-9BAC-B1483414D5D5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit4-console", "src\NUnitConsole\nunit4-console\nunit4-console.csproj", "{E1F5F8D3-4AC4-4BB0-9438-2BF41768B473}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit4-console.tests", "src\NUnitConsole\nunit4-console.tests\nunit4-console.tests.csproj", "{0D6B575A-93B4-4944-B4C0-68CDD413B904}" @@ -189,10 +187,6 @@ Global {D4AE483A-D4E1-4892-AF87-3607FBA8755F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4AE483A-D4E1-4892-AF87-3607FBA8755F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4AE483A-D4E1-4892-AF87-3607FBA8755F}.Release|Any CPU.Build.0 = Release|Any CPU - {1343AB32-5FB0-408B-9BAC-B1483414D5D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1343AB32-5FB0-408B-9BAC-B1483414D5D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1343AB32-5FB0-408B-9BAC-B1483414D5D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1343AB32-5FB0-408B-9BAC-B1483414D5D5}.Release|Any CPU.Build.0 = Release|Any CPU {E1F5F8D3-4AC4-4BB0-9438-2BF41768B473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E1F5F8D3-4AC4-4BB0-9438-2BF41768B473}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1F5F8D3-4AC4-4BB0-9438-2BF41768B473}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -264,7 +258,6 @@ Global {D5CC541C-95B7-4CB4-AADD-660FDCC281B8} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} {E4041712-A1F1-479D-A7F7-89F491066163} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} {D4AE483A-D4E1-4892-AF87-3607FBA8755F} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} - {1343AB32-5FB0-408B-9BAC-B1483414D5D5} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} {E1F5F8D3-4AC4-4BB0-9438-2BF41768B473} = {576DB1E6-C5EC-4FEF-A826-EC19D8BEE572} {0D6B575A-93B4-4944-B4C0-68CDD413B904} = {576DB1E6-C5EC-4FEF-A826-EC19D8BEE572} {9667833E-A037-41C9-A3BC-D218903EF670} = {576DB1E6-C5EC-4FEF-A826-EC19D8BEE572} diff --git a/build.cake b/build.cake index b7c5ccfcc..b7deb9f38 100644 --- a/build.cake +++ b/build.cake @@ -46,9 +46,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net6.0").WithFiles( "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/net7.0").WithFiles( - "nunit-agent-net70.dll", "nunit-agent-net70.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent-net80.dll", "nunit-agent-net80.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll") @@ -108,9 +105,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net6.0").WithFiles( "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/net7.0").WithFiles( - "nunit-agent-net70.dll", "nunit-agent-net70.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent-net80.dll", "nunit-agent-net80.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll") diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 80cc80664..82925e26e 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -70,16 +70,6 @@ - - - - - - - - - - diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index d141ef4af..ac193052a 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -90,18 +90,6 @@ - - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index c6e374ba0..c383ba9d3 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -66,19 +66,6 @@ - - - - - - - - - - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-net70/app.config b/src/NUnitEngine/agents/nunit-agent-net70/app.config deleted file mode 100644 index f72511441..000000000 --- a/src/NUnitEngine/agents/nunit-agent-net70/app.config +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-net70/app.manifest b/src/NUnitEngine/agents/nunit-agent-net70/app.manifest deleted file mode 100644 index 8f6005235..000000000 --- a/src/NUnitEngine/agents/nunit-agent-net70/app.manifest +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-net70/nunit-agent-net70.csproj b/src/NUnitEngine/agents/nunit-agent-net70/nunit-agent-net70.csproj deleted file mode 100644 index 79fe076cb..000000000 --- a/src/NUnitEngine/agents/nunit-agent-net70/nunit-agent-net70.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - Exe - nunit.agent - net7.0 - ..\..\..\..\bin\$(Configuration)\agents - app.manifest - ..\..\..\..\nunit.ico - false - 1505 - - - - - - NUnit Engine - NUnit Agent ($(TargetFramework)) - Agent used to run tests out of process - - - - - - - - - - - - - - - nunit.ico - - - - - - - - - - - $([System.IO.Path]::GetFullPath("../../../NUnitConsole/nunit4-console/bin/$(Configuration)/net462/agents/nunit-agent-net70/")) - $([System.IO.Path]::GetFullPath("../../nunit.engine/bin/$(Configuration)/agents/nunit-agent-net70/")) - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index c8a615025..b4e9657aa 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -51,8 +51,8 @@ public void SetUp() [TestCase("netcore-5.0", true, "net6.0/nunit-agent-net60.dll")] [TestCase("netcore-6.0", false, "net6.0/nunit-agent-net60.dll")] [TestCase("netcore-6.0", true, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-7.0", false, "net7.0/nunit-agent-net70.dll")] - [TestCase("netcore-7.0", true, "net7.0/nunit-agent-net70.dll")] + [TestCase("netcore-7.0", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-7.0", true, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-8.0", false, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-8.0", true, "net8.0/nunit-agent-net80.dll")] public void AgentSelection(string runtime, bool x86, string agentPath) diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 681ac4340..600757c20 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -138,13 +138,10 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re { case 9: case 8: + case 7: runtimeIdentifier = agentSubDir = "net8.0"; agentName = "nunit-agent-net80"; break; - case 7: - runtimeIdentifier = agentSubDir = "net7.0"; - agentName = "nunit-agent-net70"; - break; case 6: case 5: runtimeIdentifier = agentSubDir = "net6.0"; From 5c75ea7328a32dd192346499c721d3167e65f75b Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 18 Jan 2025 16:48:49 -0800 Subject: [PATCH 12/16] Eliminate .NET Core 3.1 agent --- NUnitConsole.sln | 7 --- build.cake | 10 ---- choco/nunit-console-runner.nuspec | 10 ---- nuget/engine/nunit.engine.nuspec | 12 ---- nuget/runners/nunit.console-runner.nuspec | 13 ---- .../agents/nunit-agent-netcore31/app.config | 41 ------------- .../agents/nunit-agent-netcore31/app.manifest | 50 ---------------- .../nunit-agent-netcore31.csproj | 60 ------------------- .../Services/AgentProcessTests.cs | 8 +-- .../nunit.engine/Services/AgentProcess.cs | 7 +-- 10 files changed, 5 insertions(+), 213 deletions(-) delete mode 100644 src/NUnitEngine/agents/nunit-agent-netcore31/app.config delete mode 100644 src/NUnitEngine/agents/nunit-agent-netcore31/app.manifest delete mode 100644 src/NUnitEngine/agents/nunit-agent-netcore31/nunit-agent-netcore31.csproj diff --git a/NUnitConsole.sln b/NUnitConsole.sln index 6067b080e..6071e231e 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -125,8 +125,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{0F76 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-net80", "src\NUnitEngine\agents\nunit-agent-net80\nunit-agent-net80.csproj", "{CD357A8B-BE40-4F63-92DA-A71B94EE47E4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit-agent-netcore31", "src\NUnitEngine\agents\nunit-agent-netcore31\nunit-agent-netcore31.csproj", "{446DAD12-87E5-47A4-B19E-D9F269ADEF72}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FakeExtensions", "src\TestData\FakeExtensions\FakeExtensions.csproj", "{1D84092C-32D7-4074-9FA6-5FA97335F173}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppContextTest", "src\TestData\AppContextTest\AppContextTest.csproj", "{E79A3CCD-07E2-4C50-B8BF-1C5844720725}" @@ -211,10 +209,6 @@ Global {CD357A8B-BE40-4F63-92DA-A71B94EE47E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD357A8B-BE40-4F63-92DA-A71B94EE47E4}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD357A8B-BE40-4F63-92DA-A71B94EE47E4}.Release|Any CPU.Build.0 = Release|Any CPU - {446DAD12-87E5-47A4-B19E-D9F269ADEF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {446DAD12-87E5-47A4-B19E-D9F269ADEF72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {446DAD12-87E5-47A4-B19E-D9F269ADEF72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {446DAD12-87E5-47A4-B19E-D9F269ADEF72}.Release|Any CPU.Build.0 = Release|Any CPU {1D84092C-32D7-4074-9FA6-5FA97335F173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1D84092C-32D7-4074-9FA6-5FA97335F173}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D84092C-32D7-4074-9FA6-5FA97335F173}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -267,7 +261,6 @@ Global {9375E2BF-5AF9-4773-A5D1-EA16D9006940} = {B25157A1-B3CD-40FB-89F2-A7206C0B553A} {0F76DA4A-58B9-4B45-BEC5-259105570D11} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} {CD357A8B-BE40-4F63-92DA-A71B94EE47E4} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} - {446DAD12-87E5-47A4-B19E-D9F269ADEF72} = {1704B7B2-5AD3-44EA-AB26-445C4E4E2C54} {1D84092C-32D7-4074-9FA6-5FA97335F173} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} {E79A3CCD-07E2-4C50-B8BF-1C5844720725} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} {48DF1E40-93BA-436A-B460-5D1130316ADA} = {37D508B2-91E0-4B32-869B-DFF9E68EA213} diff --git a/build.cake b/build.cake index b7deb9f38..bc149c82e 100644 --- a/build.cake +++ b/build.cake @@ -40,9 +40,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles( "nunit-agent-net462.exe", "nunit-agent-net462.exe.config", "nunit-agent-net462-x86.exe", "nunit-agent-net462-x86.exe.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles( - "nunit-agent-netcore31.dll", "nunit-agent-netcore31.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net6.0").WithFiles( "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), @@ -55,12 +52,8 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb", "nunit-console.pdb"), HasDirectory("tools/agents/net462").WithFiles( "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles( - "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), HasDirectory("tools/agents/net6.0").WithFiles( "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), - HasDirectory("tools/agents/net7.0").WithFiles( - "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb") }, @@ -99,9 +92,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles( "nunit-agent-net462.exe", "nunit-agent-net462.exe.config", "nunit-agent-net462-x86.exe", "nunit-agent-net462-x86.exe.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/netcoreapp3.1").WithFiles( - "nunit-agent-netcore31.dll", "nunit-agent-netcore31.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net6.0").WithFiles( "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 82925e26e..2298b8713 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -50,16 +50,6 @@ - - - - - - - - - - diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index ac193052a..a6bcfbd81 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -66,18 +66,6 @@ - - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index c383ba9d3..2eed47789 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -40,19 +40,6 @@ - - - - - - - - - - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-netcore31/app.config b/src/NUnitEngine/agents/nunit-agent-netcore31/app.config deleted file mode 100644 index f72511441..000000000 --- a/src/NUnitEngine/agents/nunit-agent-netcore31/app.config +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-netcore31/app.manifest b/src/NUnitEngine/agents/nunit-agent-netcore31/app.manifest deleted file mode 100644 index 8f6005235..000000000 --- a/src/NUnitEngine/agents/nunit-agent-netcore31/app.manifest +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - diff --git a/src/NUnitEngine/agents/nunit-agent-netcore31/nunit-agent-netcore31.csproj b/src/NUnitEngine/agents/nunit-agent-netcore31/nunit-agent-netcore31.csproj deleted file mode 100644 index 62ca5ee1b..000000000 --- a/src/NUnitEngine/agents/nunit-agent-netcore31/nunit-agent-netcore31.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - - Exe - nunit.agent - netcoreapp3.1 - ..\..\..\..\bin\$(Configuration)\agents - app.manifest - ..\..\..\..\nunit.ico - false - - $(NoWarn);SYSLIB0011;SYSLIB0012;1505 - - - - - - NUnit Engine - NUnit Agent ($(TargetFramework)) - Agent used to run tests out of process - - - - - - - - - - - - - - - nunit.ico - - - - - - - - - - - $([System.IO.Path]::GetFullPath("../../../NUnitConsole/nunit4-console/bin/$(Configuration)/net462/agents/nunit-agent-netcore31/")) - $([System.IO.Path]::GetFullPath("../../nunit.engine/bin/$(Configuration)/agents/nunit-agent-netcore31/")) - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index b4e9657aa..5f17f2938 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -43,10 +43,10 @@ public void SetUp() [TestCase("net-3.5", true, "net462/nunit-agent-net462-x86.exe")] [TestCase("net-2.0", false, "net462/nunit-agent-net462.exe")] [TestCase("net-2.0", true, "net462/nunit-agent-net462-x86.exe")] - [TestCase("netcore-2.1", false, "netcoreapp3.1/nunit-agent-netcore31.dll")] - [TestCase("netcore-2.1", true, "netcoreapp3.1/nunit-agent-netcore31.dll")] - [TestCase("netcore-3.1", false, "netcoreapp3.1/nunit-agent-netcore31.dll")] - [TestCase("netcore-3.1", true, "netcoreapp3.1/nunit-agent-netcore31.dll")] + [TestCase("netcore-2.1", false, "net6.0/nunit-agent-net60.dll")] + [TestCase("netcore-2.1", true, "net6.0/nunit-agent-net60.dll")] + [TestCase("netcore-3.1", false, "net6.0/nunit-agent-net60.dll")] + [TestCase("netcore-3.1", true, "net6.0/nunit-agent-net60.dll")] [TestCase("netcore-5.0", false, "net6.0/nunit-agent-net60.dll")] [TestCase("netcore-5.0", true, "net6.0/nunit-agent-net60.dll")] [TestCase("netcore-6.0", false, "net6.0/nunit-agent-net60.dll")] diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 600757c20..0b27e58c0 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -142,15 +142,10 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re runtimeIdentifier = agentSubDir = "net8.0"; agentName = "nunit-agent-net80"; break; - case 6: - case 5: + default: runtimeIdentifier = agentSubDir = "net6.0"; agentName = "nunit-agent-net60"; break; - default: - runtimeIdentifier = agentSubDir = "netcoreapp3.1"; - agentName = "nunit-agent-netcore31"; - break; } agentExtension = ".dll"; break; From 048d8bf2a27040a819f4f0398aff74573b45a9a1 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 18 Jan 2025 16:50:36 -0800 Subject: [PATCH 13/16] We still need .NET 7.0 for our test assemblies --- .github/workflows/NUnitConsoleAndEngine.CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/NUnitConsoleAndEngine.CI.yml b/.github/workflows/NUnitConsoleAndEngine.CI.yml index d218973ba..da666aeb2 100644 --- a/.github/workflows/NUnitConsoleAndEngine.CI.yml +++ b/.github/workflows/NUnitConsoleAndEngine.CI.yml @@ -37,6 +37,7 @@ jobs: dotnet-version: | 3.1.x 6.0.x + 7.0.x 8.0.100 - name: 🔧 Install dotnet tools From 2947d26ac5fa4eab13259d486d5d23f8fd3fd915 Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sat, 18 Jan 2025 17:35:01 -0800 Subject: [PATCH 14/16] Drop .Net 6.0 agent --- build.cake | 19 ++++++------------- choco/nunit-console-runner.nuspec | 10 ---------- nuget/engine/nunit.engine.nuspec | 12 ------------ nuget/runners/nunit.console-runner.nuspec | 13 ------------- .../Services/AgentProcessTests.cs | 18 ++++++++++-------- .../nunit.engine/Services/AgentProcess.cs | 8 ++------ 6 files changed, 18 insertions(+), 62 deletions(-) diff --git a/build.cake b/build.cake index bc149c82e..8433cde82 100644 --- a/build.cake +++ b/build.cake @@ -40,9 +40,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles( "nunit-agent-net462.exe", "nunit-agent-net462.exe.config", "nunit-agent-net462-x86.exe", "nunit-agent-net462-x86.exe.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/net6.0").WithFiles( - "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent-net80.dll", "nunit-agent-net80.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll") @@ -52,8 +49,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb", "nunit-console.pdb"), HasDirectory("tools/agents/net462").WithFiles( "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), - HasDirectory("tools/agents/net6.0").WithFiles( - "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb") }, @@ -92,9 +87,6 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("tools/agents/net462").WithFiles( "nunit-agent-net462.exe", "nunit-agent-net462.exe.config", "nunit-agent-net462-x86.exe", "nunit-agent-net462-x86.exe.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), - HasDirectory("tools/agents/net6.0").WithFiles( - "nunit-agent-net60.dll", "nunit-agent-net60.dll.config", - "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), HasDirectory("tools/agents/net8.0").WithFiles( "nunit-agent-net80.dll", "nunit-agent-net80.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll") @@ -115,18 +107,19 @@ BuildSettings.Packages.AddRange(new PackageDefinition[] { HasDirectory("agents/net462").WithFiles( "nunit-agent-net462.exe", "nunit-agent-net462.exe.config", "nunit-agent-net462-x86.exe", "nunit-agent-net462-x86.exe.config", + "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll"), + HasDirectory("agents/net8.0").WithFiles( + "nunit-agent-net80.dll", "nunit-agent-net80.dll.config", "nunit.engine.core.dll", "nunit.engine.api.dll", "testcentric.metadata.dll") }, symbols: new PackageCheck[] { HasDirectory("lib/net462").WithFiles( "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), - HasDirectory("lib/netstandard2.0").WithFiles( - "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), - HasDirectory("lib/net6.0").WithFiles( - "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), HasDirectory("lib/net8.0").WithFiles( "nunit.engine.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), HasDirectory("contentFiles/any/agents/net462").WithFiles( - "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb") + "nunit-agent.pdb", "nunit-agent-x86.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb"), + HasDirectory("contentFiles/any/agents/net8.0").WithFiles( + "nunit-agent.pdb", "nunit.engine.core.pdb", "nunit.engine.api.pdb") }), NUnitEngineApiPackage = new NuGetPackage( diff --git a/choco/nunit-console-runner.nuspec b/choco/nunit-console-runner.nuspec index 2298b8713..d197cf1c6 100644 --- a/choco/nunit-console-runner.nuspec +++ b/choco/nunit-console-runner.nuspec @@ -50,16 +50,6 @@ - - - - - - - - - - diff --git a/nuget/engine/nunit.engine.nuspec b/nuget/engine/nunit.engine.nuspec index a6bcfbd81..5329cd155 100644 --- a/nuget/engine/nunit.engine.nuspec +++ b/nuget/engine/nunit.engine.nuspec @@ -66,18 +66,6 @@ - - - - - - - - - - - - diff --git a/nuget/runners/nunit.console-runner.nuspec b/nuget/runners/nunit.console-runner.nuspec index 2eed47789..b6cf0989a 100644 --- a/nuget/runners/nunit.console-runner.nuspec +++ b/nuget/runners/nunit.console-runner.nuspec @@ -40,19 +40,6 @@ - - - - - - - - - - - - - diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs index 5f17f2938..e2d695c61 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentProcessTests.cs @@ -43,18 +43,20 @@ public void SetUp() [TestCase("net-3.5", true, "net462/nunit-agent-net462-x86.exe")] [TestCase("net-2.0", false, "net462/nunit-agent-net462.exe")] [TestCase("net-2.0", true, "net462/nunit-agent-net462-x86.exe")] - [TestCase("netcore-2.1", false, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-2.1", true, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-3.1", false, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-3.1", true, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-5.0", false, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-5.0", true, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-6.0", false, "net6.0/nunit-agent-net60.dll")] - [TestCase("netcore-6.0", true, "net6.0/nunit-agent-net60.dll")] + [TestCase("netcore-2.1", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-2.1", true, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-3.1", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-3.1", true, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-5.0", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-5.0", true, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-6.0", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-6.0", true, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-7.0", false, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-7.0", true, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-8.0", false, "net8.0/nunit-agent-net80.dll")] [TestCase("netcore-8.0", true, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-9.0", false, "net8.0/nunit-agent-net80.dll")] + [TestCase("netcore-9.0", true, "net8.0/nunit-agent-net80.dll")] public void AgentSelection(string runtime, bool x86, string agentPath) { _package.Settings[EnginePackageSettings.TargetRuntimeFramework] = runtime; diff --git a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs index 0b27e58c0..f15504eaf 100644 --- a/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs +++ b/src/NUnitEngine/nunit.engine/Services/AgentProcess.cs @@ -137,15 +137,11 @@ public static string GetTestAgentExePath(RuntimeFramework targetRuntime, bool re switch (major) { case 9: - case 8: - case 7: + // Case for .NET 9.0 agent will go here + default: runtimeIdentifier = agentSubDir = "net8.0"; agentName = "nunit-agent-net80"; break; - default: - runtimeIdentifier = agentSubDir = "net6.0"; - agentName = "nunit-agent-net60"; - break; } agentExtension = ".dll"; break; From ff04e8e55c066620269d7ba6bd8d1f495ce8462a Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Sun, 19 Jan 2025 20:47:56 -0800 Subject: [PATCH 15/16] Changes from review --- package-tests.cake | 6 +- .../Extensibility/IFrameworkDriver.cs | 2 +- .../Drivers/NUnitFrameworkDriverTests.cs | 33 ++++++++- .../NotRunnableFrameworkDriverTests.cs | 6 +- .../Runners/TestAgentRunnerTests.cs | 2 +- .../Services/DriverServiceTests.cs | 7 +- .../Drivers/DriverService.cs | 18 ++--- .../Drivers/NUnit3DriverFactory.cs | 1 + .../Drivers/NUnitFrameworkApi.cs | 7 +- .../Drivers/NUnitFrameworkApi2009.cs | 34 +++++---- .../Drivers/NUnitFrameworkApi2018.cs | 70 ++++--------------- .../Drivers/NUnitFrameworkDriver.cs | 49 +++++++++---- .../Drivers/NotRunnableFrameworkDriver.cs | 23 +++--- .../Runners/TestAgentRunner.cs | 2 - .../nunit.engine/Runners/TestEngineRunner.cs | 1 - src/TestData/NUnit3.0.1/NUnit3.0.1.csproj | 4 +- src/TestData/NUnit3.10/NUnit3.10.csproj | 4 +- src/TestData/NUnit3.2/NUnit3.2.csproj | 4 +- 18 files changed, 144 insertions(+), 129 deletions(-) diff --git a/package-tests.cake b/package-tests.cake index 8050d442a..9bfdfe3b3 100644 --- a/package-tests.cake +++ b/package-tests.cake @@ -158,7 +158,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "NUnit301Test", "Run a test under NUnit 3.0.1 using 2009 API", - "testdata/NUnit3.0/net462/NUnit3.0.1.dll", + "testdata/NUnit3.0.1/net462/NUnit3.0.1.dll", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.0.1.dll", "net462") } @@ -167,7 +167,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "NUnit32Test", "Run a test under NUnit 3.2 using 20018 API", - "testdata/NUnit3.0/net462/NUnit3.2.dll", + "testdata/NUnit3.2/net462/NUnit3.2.dll", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.2.dll", "net462") } @@ -176,7 +176,7 @@ StandardRunnerTests.Add(new PackageTest( StandardRunnerTests.Add(new PackageTest( 1, "NUnit310Test", "Run a test under NUnit 3.10 using 2018 API", - "testdata/NUnit3.0/net462/NUnit3.10.dll", + "testdata/NUnit3.10/net462/NUnit3.10.dll", new ExpectedResult("Passed") { Assemblies = new[] { new ExpectedAssemblyResult("NUnit3.10.dll", "net462") } diff --git a/src/NUnitEngine/nunit.engine.api/Extensibility/IFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.api/Extensibility/IFrameworkDriver.cs index 3f8f190bb..46ed1f841 100644 --- a/src/NUnitEngine/nunit.engine.api/Extensibility/IFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.api/Extensibility/IFrameworkDriver.cs @@ -18,7 +18,7 @@ public interface IFrameworkDriver /// Gets and sets the unique identifier for this driver, /// used to ensure that test ids are unique across drivers. /// - string ID { get; set; } + string ID { get; } /// /// Loads the tests in an assembly. diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs index 76cfa2339..d317e9b4f 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs @@ -126,14 +126,41 @@ public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() } #if NETFRAMEWORK - //[Test] - public void RunTestsAction_WithInvalidFilterElement_ThrowsNUnitEngineException() + // Nested Class tests Api Selection in the driver + public class ApiSelectionTests() + { + [TestCase("4.2.2", "2018")] + [TestCase("3.14.0", "2018")] + [TestCase("3.2.0", "2018")] + [TestCase("3.0.1", "2009")] + [TestCase("3.0.0", "2009")] + public void CorrectApiIsSelected(string nunitVersion, string apiVersion) + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, "99", new AssemblyName() + { + Name = "nunit.framework", + Version = new Version(nunitVersion) + }); + + Assert.That(driver.API, Is.EqualTo(apiVersion)); + } + } + + [Test] + public void RunTestsAction_WithInvalidFilterElement_ThrowsException() { _driver.Load(_mockAssemblyPath, _settings); var invalidFilter = "foo"; var ex = Assert.Catch(() => _driver.Run(new NullListener(), invalidFilter)); - Assert.That(ex, Is.TypeOf()); + + if (_whichApi == "2018") + { + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.InnerException, Is.TypeOf()); + } + else + Assert.That(ex, Is.TypeOf()); } private class CallbackEventHandler : System.Web.UI.ICallbackEventHandler diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NotRunnableFrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NotRunnableFrameworkDriverTests.cs index 0119e74ed..4bd37a513 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NotRunnableFrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NotRunnableFrameworkDriverTests.cs @@ -14,7 +14,6 @@ namespace NUnit.Engine.Drivers // Functional tests of the NUnitFrameworkDriver calling into the framework. public abstract class NotRunnableFrameworkDriverTests { - private const string DRIVER_ID = "99"; private const string EXPECTED_ID = "99-1"; protected string? _expectedRunState; @@ -95,7 +94,6 @@ public void Run(string filePath, string expectedType) private IFrameworkDriver GetDriver(string filePath) { IFrameworkDriver driver = CreateDriver(filePath); - driver.ID = DRIVER_ID; return driver; } @@ -126,7 +124,7 @@ public InvalidAssemblyFrameworkDriverTests() protected override IFrameworkDriver CreateDriver(string filePath) { - return new InvalidAssemblyFrameworkDriver(filePath, _expectedReason ?? "Not Specified"); + return new InvalidAssemblyFrameworkDriver(filePath, "99", _expectedReason ?? "Not Specified"); } } @@ -142,7 +140,7 @@ public SkippedAssemblyFrameworkDriverTests() protected override IFrameworkDriver CreateDriver(string filePath) { - return new SkippedAssemblyFrameworkDriver(filePath); + return new SkippedAssemblyFrameworkDriver(filePath, "99"); } } } diff --git a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs index 1d9edf5fb..b02f94a7a 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Runners/TestAgentRunnerTests.cs @@ -69,7 +69,7 @@ public void Run() CheckPackageLoading(); } - //[Test] + [Test] public void RunAsync() { var asyncResult = _runner.RunAsync(null, TestFilter.Empty); diff --git a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs index 5cfc5b2a4..538263071 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Services/DriverServiceTests.cs @@ -7,7 +7,7 @@ using NUnit.Engine.Drivers; using NUnit.Engine.Extensibility; -namespace NUnit.Engine.Services.Tests +namespace NUnit.Engine.Services { [TestFixture] public class DriverServiceTests @@ -30,11 +30,10 @@ public void CorrectDriverIsUsed(string fileName, bool skipNonTestAssemblies, Typ static TestCaseData[] DriverSelectionTestCases = new[] { - // TODO: make commented tests work new TestCaseData("mock-assembly.dll", false, typeof(NUnitFrameworkDriver)), new TestCaseData("mock-assembly.dll", true, typeof(NUnitFrameworkDriver)), - //new TestCaseData("notest-assembly.dll", false, typeof(NUnitFrameworkDriver)), - //new TestCaseData"notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)) + new TestCaseData("notest-assembly.dll", false, typeof(NUnitFrameworkDriver)).Ignore("Assembly not present"), + new TestCaseData("notest-assembly.dll", true, typeof(SkippedAssemblyFrameworkDriver)).Ignore("Assembly not present"), // Invalid cases should work with all target runtimes new TestCaseData("mock-assembly.pdb", false, typeof(InvalidAssemblyFrameworkDriver)), diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs index 8a88c1a98..48d33c8d3 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs @@ -54,10 +54,10 @@ public DriverService() public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string assemblyPath, string? targetFramework, bool skipNonTestAssemblies) { if (!File.Exists(assemblyPath)) - return new InvalidAssemblyFrameworkDriver(assemblyPath, "File not found: " + assemblyPath); + return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, "File not found: " + assemblyPath); if (!PathUtils.IsAssemblyFileType(assemblyPath)) - return new InvalidAssemblyFrameworkDriver(assemblyPath, "File type is not supported"); + return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, "File type is not supported"); if (targetFramework != null) { @@ -70,9 +70,9 @@ public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string if (platform == "Silverlight" || platform == ".NETPortable" || platform == ".NETStandard" || platform == ".NETCompactFramework") if (skipNonTestAssemblies) - return new SkippedAssemblyFrameworkDriver(assemblyPath); + return new SkippedAssemblyFrameworkDriver(assemblyPath, "X"); else - return new InvalidAssemblyFrameworkDriver(assemblyPath, platform + + return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, platform + " test assemblies are not supported by this version of the engine"); } @@ -84,7 +84,7 @@ public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string { foreach (var attr in assemblyDef.CustomAttributes) if (attr.AttributeType.FullName == "NUnit.Framework.NonTestAssemblyAttribute") - return new SkippedAssemblyFrameworkDriver(assemblyPath); + return new SkippedAssemblyFrameworkDriver(assemblyPath, package.ID); } foreach (var factory in _factories) @@ -108,14 +108,14 @@ public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string } catch (BadImageFormatException ex) { - return new InvalidAssemblyFrameworkDriver(assemblyPath, ex.Message); + return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, ex.Message); } if (skipNonTestAssemblies) - return new SkippedAssemblyFrameworkDriver(assemblyPath); + return new SkippedAssemblyFrameworkDriver(assemblyPath, package.ID); else - return new InvalidAssemblyFrameworkDriver(assemblyPath, string.Format("No suitable tests found in '{0}'.\n" + - "Either assembly contains no tests or proper test driver has not been found.", assemblyPath)); + return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, + $"No suitable tests found in '{assemblyPath}'.\r\nEither assembly contains no tests or proper test driver has not been found."); } } } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs index 1e3e44f2f..7205e8d0a 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnit3DriverFactory.cs @@ -46,6 +46,7 @@ public IFrameworkDriver GetDriver(AppDomain domain, string id, AssemblyName refe /// public IFrameworkDriver GetDriver(string id, AssemblyName reference) { + Guard.ArgumentNotNullOrEmpty(id, nameof(id)); Guard.ArgumentValid(IsSupportedTestFramework(reference), "Invalid framework", "reference"); log.Info("Using NUnitFrameworkDriver"); return new NUnitFrameworkDriver(id, reference); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs index d84a05cf3..8af86e081 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi.cs @@ -21,13 +21,18 @@ public interface NUnitFrameworkApi int CountTestCases(string filter); /// - /// Executes the tests in an assembly. + /// Executes the tests in an assembly synchronously. /// /// An ITestEventHandler that receives progress notices /// A XML string representing the filter that controls which tests are executed /// An Xml string representing the result string Run(ITestEventListener? listener, string filter); + /// + /// Executes the tests in an assembly asynchronously. + /// + /// A callback that receives XML progress notices + /// A filter that controls which tests are executed void RunAsync(Action callback, string filter); /// diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs index 170a3daca..4fb9b40fa 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs @@ -1,5 +1,6 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +#if NETFRAMEWORK using System; using System.Collections.Generic; using System.IO; @@ -10,7 +11,6 @@ namespace NUnit.Engine.Drivers { -#if NETFRAMEWORK /// /// This is the original NUnit 3 API, which only works for .NET Framework. /// As far as I can discover, it first appeared in pre-release 2.9.1, @@ -27,15 +27,15 @@ class NUnitFrameworkApi2009 : NUnitFrameworkApi const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - string _driverId; + private readonly string _driverId; - AppDomain _testDomain; - AssemblyName _nunitRef; + private readonly AppDomain _testDomain; + private readonly AssemblyName _nunitRef; - string? _testAssemblyPath; + private string? _testAssemblyPath; - object? _frameworkController; - Type? _frameworkControllerType; + private object? _frameworkController; + private Type? _frameworkControllerType; public NUnitFrameworkApi2009(AppDomain testDomain, string driverId, AssemblyName nunitRef) { @@ -60,11 +60,19 @@ public string Load(string testAssemblyPath, IDictionary settings var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - var idPrefix = string.IsNullOrEmpty(_driverId) ? "" : _driverId + "-"; + var idPrefix = _driverId + "-"; try { - _frameworkController = CreateObject(CONTROLLER_TYPE, _testAssemblyPath, idPrefix, settings); + _frameworkController = _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, + CONTROLLER_TYPE, + false, + 0, + null, + new object[] { _testAssemblyPath, idPrefix, settings }, + null, + null).ShouldNotBeNull(); } catch (BadImageFormatException ex) when (requestedRuntime != null) { @@ -144,8 +152,10 @@ private object CreateObject(string typeName, params object?[]? args) { try { - return _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + return _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + //return _testDomain.CreateInstanceAndUnwrap( + // _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; } catch (TargetInvocationException ex) { @@ -153,5 +163,5 @@ private object CreateObject(string typeName, params object?[]? args) } } } -#endif } +#endif diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs index b6da7a9ac..8d5778764 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs @@ -4,12 +4,9 @@ using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Runtime.Remoting; using System.Runtime.Serialization; using NUnit.Common; -using NUnit.Engine.Extensibility; using NUnit.Engine.Internal; -using TestCentric.Metadata; namespace NUnit.Engine.Drivers { @@ -34,14 +31,20 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi const string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; - private string _driverId; + private readonly string _driverId; - AssemblyName _nunitRef; - string? _testAssemblyPath; + private readonly AssemblyName _nunitRef; - object? _frameworkController; - Type? _frameworkControllerType; + private object? _frameworkController; + private Type? _frameworkControllerType; +#if NETCOREAPP + private TestAssemblyLoadContext? _assemblyLoadContext; + //private Assembly? _testAssembly; + private Assembly? _frameworkAssembly; +#endif + + private string? _testAssemblyPath; public NUnitFrameworkApi2018(string driverId, AssemblyName nunitRef) { @@ -49,27 +52,9 @@ public NUnitFrameworkApi2018(string driverId, AssemblyName nunitRef) Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); _driverId = driverId; -#if NETFRAMEWORK - //if (RemotingServices.IsTransparentProxy(driver)) - //{ - // // We need to replace nunitRef with a reference valid in the current domain. - // var frameworkRef = AssemblyNameReference.Parse(nunitRef.FullName); - // var frameworkDef = new DefaultAssemblyResolver().Resolve(frameworkRef); - // frameworkDef.MainModule. - //} - //if (_nunitRef == null) - _nunitRef = nunitRef; -#else _nunitRef = nunitRef; -#endif } -#if NETCOREAPP - TestAssemblyLoadContext? _assemblyLoadContext; - Assembly? _testAssembly; - Assembly? _frameworkAssembly; -#endif - public string Load(string testAssemblyPath, IDictionary settings) { Guard.ArgumentNotNull(testAssemblyPath, nameof(testAssemblyPath)); @@ -81,10 +66,6 @@ public string Load(string testAssemblyPath, IDictionary settings var idPrefix = string.IsNullOrEmpty(_driverId) ? "" : _driverId + "-"; #if NETFRAMEWORK - // Normally, the caller should check for an invalid requested runtime, but we make sure here - var requestedRuntime = settings.ContainsKey(EnginePackageSettings.RequestedRuntimeFramework) - ? settings[EnginePackageSettings.RequestedRuntimeFramework] : null; - try { _frameworkController = AppDomain.CurrentDomain.CreateInstanceAndUnwrap( @@ -97,14 +78,6 @@ public string Load(string testAssemblyPath, IDictionary settings null, null).ShouldNotBeNull(); } - catch (BadImageFormatException ex) when (requestedRuntime != null) - { - throw new NUnitEngineException($"Requested runtime {requestedRuntime} is not suitable for use with test assembly {_testAssemblyPath}", ex); - } - catch (SerializationException ex) - { - throw new NUnitEngineException("The NUnit 3 driver cannot support this test assembly. Use a platform specific runner.", ex); - } catch (Exception ex) { string msg = $"Failed to load {_nunitRef.FullName}\r\n Codebase: {_nunitRef.CodeBase}"; @@ -119,10 +92,10 @@ public string Load(string testAssemblyPath, IDictionary settings #else _assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath); - _testAssembly = LoadAssembly(testAssemblyPath); + var testAssembly = LoadAssembly(testAssemblyPath); _frameworkAssembly = LoadAssembly(_nunitRef); - _frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings); + _frameworkController = CreateInstance(CONTROLLER_TYPE, testAssembly, idPrefix, settings); if (_frameworkController == null) { log.Error(INVALID_FRAMEWORK_MESSAGE); @@ -148,7 +121,7 @@ public string Run(ITestEventListener? listener, string filter) { CheckLoadWasCalled(); log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath.ShouldNotBeNull())); - Action? callback = /*listener != null ? listener.OnTestEvent :*/ (Action?)null; + Action? callback = null; return (string)ExecuteMethod(RUN_METHOD, new[] { typeof(Action), typeof(string) }, callback, filter); } @@ -177,20 +150,7 @@ private void CheckLoadWasCalled() throw new InvalidOperationException(LOAD_MESSAGE); } -#if NETFRAMEWORK - //private ObjectHandle? CreateInstance(string typeName, params object?[]? args) - //{ - // try - // { - // return _testDomain.CreateInstance( - // _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; - // } - // catch (TargetInvocationException ex) - // { - // throw new NUnitEngineException("The NUnit 3 driver encountered an error while executing reflected code.", ex.InnerException); - // } - //} -#else +#if NETCOREAPP private object CreateInstance(string typeName, params object?[]? args) { var type = _frameworkAssembly.ShouldNotBeNull().GetType(typeName, throwOnError: true)!; diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index 2d37c30ab..5c082e7cc 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -2,12 +2,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Reflection; using NUnit.Common; using NUnit.Engine.Internal; using NUnit.Engine.Extensibility; -using NUnit.Engine.Runners; namespace NUnit.Engine.Drivers { @@ -17,6 +15,7 @@ namespace NUnit.Engine.Drivers /// public class NUnitFrameworkDriver : IFrameworkDriver { + static readonly Version MINIMUM_NUNIT_VERSION = new Version(3, 2, 0); static readonly ILogger log = InternalTrace.GetLogger(nameof(NUnitFrameworkDriver)); readonly NUnitFrameworkApi _api; @@ -28,10 +27,35 @@ public class NUnitFrameworkDriver : IFrameworkDriver /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. public NUnitFrameworkDriver(AppDomain testDomain, string id, AssemblyName nunitRef) - : this(testDomain, nunitRef.Version >= new Version(3,2,0) ? "2018" : "2009", id, nunitRef) { } + { + Guard.ArgumentNotNull(testDomain, nameof(testDomain)); + Guard.ArgumentNotNullOrEmpty(id, nameof(id)); + Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); + + ID = id; + + if (nunitRef.Version >= MINIMUM_NUNIT_VERSION) + { + API = "2018"; + _api = (NUnitFrameworkApi)testDomain.CreateInstanceFromAndUnwrap( + Assembly.GetExecutingAssembly().Location, + "NUnit.Engine.Drivers.NUnitFrameworkApi2018", + false, + 0, + null, + new object[] { ID, nunitRef }, + null, + null).ShouldNotBeNull(); + } + else + { + API = "2009"; + _api = new NUnitFrameworkApi2009(testDomain, ID, nunitRef); + } + } /// - /// Internal generic constructor used directly by our tests. + /// Internal generic constructor used by our tests. /// /// The application domain in which to create the FrameworkController /// An AssemblyName referring to the test framework. @@ -44,11 +68,12 @@ internal NUnitFrameworkDriver(AppDomain testDomain, string api, string id, Assem Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); ID = id; + API = api; _api = api == "2018" ? (NUnitFrameworkApi)testDomain.CreateInstanceFromAndUnwrap( Assembly.GetExecutingAssembly().Location, - $"NUnit.Engine.Drivers.NUnitFrameworkApi{api}", + typeof(NUnitFrameworkApi2018).FullName!, false, 0, null, @@ -57,12 +82,6 @@ internal NUnitFrameworkDriver(AppDomain testDomain, string api, string id, Assem null).ShouldNotBeNull() : new NUnitFrameworkApi2009(testDomain, ID, nunitRef); } - - private Assembly? TestDomain_AssemblyResolve(object? sender, ResolveEventArgs args) - { - //var fileName = args.Name; - return null; - } #else /// /// Construct an NUnitFrameworkDriver @@ -74,16 +93,22 @@ public NUnitFrameworkDriver(string id, AssemblyName nunitRef) Guard.ArgumentNotNull(nunitRef, nameof(nunitRef)); ID = id; + API = "2018"; _api = new NUnitFrameworkApi2018(ID, nunitRef); } #endif + /// + /// String naming the API in use, for use by tests + /// + internal string API { get; } = string.Empty; + /// /// An id prefix that will be passed to the test framework and used as part of the /// test ids created. /// - public string ID { get; set; } = string.Empty; + public string ID { get; } = string.Empty; /// /// Loads the tests in an assembly. diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NotRunnableFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NotRunnableFrameworkDriver.cs index 6967be78f..e7ceab661 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NotRunnableFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NotRunnableFrameworkDriver.cs @@ -36,16 +36,17 @@ public abstract class NotRunnableFrameworkDriver : IFrameworkDriver protected string _label; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - public NotRunnableFrameworkDriver(string assemblyPath, string message) + public NotRunnableFrameworkDriver(string assemblyPath, string id, string message) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. { _name = Escape(Path.GetFileName(assemblyPath)); _fullname = Escape(Path.GetFullPath(assemblyPath)); _message = Escape(message); _type = new List { ".dll", ".exe" }.Contains(Path.GetExtension(assemblyPath)) ? "Assembly" : "Unknown"; + ID = id; } - public string ID { get; set; } + public string ID { get; } public string Load(string assemblyPath, IDictionary settings) @@ -90,21 +91,13 @@ private string GetLoadResult() _type, TestID, _name, _fullname, _runstate, _message); } - private string TestID - { - get - { - return string.IsNullOrEmpty(ID) - ? "1" - : ID + "-1"; - } - } + private string TestID => ID + "-1"; } public class InvalidAssemblyFrameworkDriver :NotRunnableFrameworkDriver { - public InvalidAssemblyFrameworkDriver(string assemblyPath, string message) - : base(assemblyPath, message) + public InvalidAssemblyFrameworkDriver(string assemblyPath, string id, string message) + : base(assemblyPath, id, message) { _runstate = "NotRunnable"; _result = "Failed"; @@ -114,8 +107,8 @@ public InvalidAssemblyFrameworkDriver(string assemblyPath, string message) public class SkippedAssemblyFrameworkDriver : NotRunnableFrameworkDriver { - public SkippedAssemblyFrameworkDriver(string assemblyPath) - : base(assemblyPath, "Skipping non-test assembly") + public SkippedAssemblyFrameworkDriver(string assemblyPath, string id) + : base(assemblyPath, id, "Skipping non-test assembly") { _runstate = "Runnable"; _result = "Skipped"; diff --git a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs index 83cde05df..5f2fe6dfc 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs @@ -115,8 +115,6 @@ public virtual TestEngineResult Load() _driver = DriverService.GetDriver(TestDomain, assemblyPackage, testFile, targetFramework, skipNonTestAssemblies); - _driver.ID = assemblyPackage.ID; - try { return new TestEngineResult(_driver.Load(testFile, assemblyPackage.Settings)); diff --git a/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs b/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs index bd9e2188e..521e5e36d 100644 --- a/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/TestEngineRunner.cs @@ -3,7 +3,6 @@ using System; using System.ComponentModel; using NUnit.Common; -using NUnit.Engine.Internal; namespace NUnit.Engine.Runners { diff --git a/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj b/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj index ede18b3e3..a88c94326 100644 --- a/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj +++ b/src/TestData/NUnit3.0.1/NUnit3.0.1.csproj @@ -3,11 +3,11 @@ NUnit.TestData net462 - ../../../bin/$(Configuration)/testdata/nunit3.0 + ../../../bin/$(Configuration)/testdata/nunit3.0.1 - + diff --git a/src/TestData/NUnit3.10/NUnit3.10.csproj b/src/TestData/NUnit3.10/NUnit3.10.csproj index ede18b3e3..ac6ddfb5f 100644 --- a/src/TestData/NUnit3.10/NUnit3.10.csproj +++ b/src/TestData/NUnit3.10/NUnit3.10.csproj @@ -3,11 +3,11 @@ NUnit.TestData net462 - ../../../bin/$(Configuration)/testdata/nunit3.0 + ../../../bin/$(Configuration)/testdata/nunit3.10 - + diff --git a/src/TestData/NUnit3.2/NUnit3.2.csproj b/src/TestData/NUnit3.2/NUnit3.2.csproj index ede18b3e3..d0a9e0376 100644 --- a/src/TestData/NUnit3.2/NUnit3.2.csproj +++ b/src/TestData/NUnit3.2/NUnit3.2.csproj @@ -3,11 +3,11 @@ NUnit.TestData net462 - ../../../bin/$(Configuration)/testdata/nunit3.0 + ../../../bin/$(Configuration)/testdata/nunit3.2 - + From db7bb19ffd53f23e48c4556cd68dfda9bc2a8a7c Mon Sep 17 00:00:00 2001 From: Charlie Poole Date: Mon, 20 Jan 2025 08:03:57 -0800 Subject: [PATCH 16/16] Further changes that were not pushed to 1596 --- .../Drivers/NUnitFrameworkDriverTests.cs | 38 +++++++++---------- .../Drivers/DriverService.cs | 2 +- .../Drivers/NUnitFrameworkApi2009.cs | 24 ++++++------ .../Drivers/NUnitFrameworkApi2018.cs | 11 +++--- .../Drivers/NUnitFrameworkDriver.cs | 2 +- .../Runners/TestAgentRunner.cs | 2 - 6 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs index d317e9b4f..011c6b9c7 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/Drivers/NUnitFrameworkDriverTests.cs @@ -14,8 +14,8 @@ namespace NUnit.Engine.Drivers // Functional tests of the NUnitFrameworkDriver calling into the framework. #if NETFRAMEWORK [TestFixture("2009")] - [TestFixture("2018")] #endif + [TestFixture("2018")] public class NUnitFrameworkDriverTests { private const string MOCK_ASSEMBLY = "mock-assembly.dll"; @@ -26,13 +26,11 @@ public class NUnitFrameworkDriverTests private NUnitFrameworkDriver _driver; private string _mockAssemblyPath; -#if NETFRAMEWORK private string _whichApi; public NUnitFrameworkDriverTests(string whichApi) { _whichApi = whichApi; } -#endif [SetUp] public void CreateDriver() @@ -125,6 +123,23 @@ public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() Assert.That(ex.Message, Is.EqualTo(LOAD_MESSAGE)); } + [Test] + public void RunTestsAction_WithInvalidFilterElement_ThrowsException() + { + _driver.Load(_mockAssemblyPath, _settings); + + var invalidFilter = "foo"; + var ex = Assert.Catch(() => _driver.Run(new NullListener(), invalidFilter)); + + if (_whichApi == "2018") + { + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.InnerException, Is.TypeOf()); + } + else + Assert.That(ex, Is.TypeOf()); + } + #if NETFRAMEWORK // Nested Class tests Api Selection in the driver public class ApiSelectionTests() @@ -146,23 +161,6 @@ public void CorrectApiIsSelected(string nunitVersion, string apiVersion) } } - [Test] - public void RunTestsAction_WithInvalidFilterElement_ThrowsException() - { - _driver.Load(_mockAssemblyPath, _settings); - - var invalidFilter = "foo"; - var ex = Assert.Catch(() => _driver.Run(new NullListener(), invalidFilter)); - - if (_whichApi == "2018") - { - Assert.That(ex, Is.TypeOf()); - Assert.That(ex.InnerException, Is.TypeOf()); - } - else - Assert.That(ex, Is.TypeOf()); - } - private class CallbackEventHandler : System.Web.UI.ICallbackEventHandler { private string? _result; diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs index 48d33c8d3..802a2e5ff 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/DriverService.cs @@ -70,7 +70,7 @@ public IFrameworkDriver GetDriver(AppDomain domain, TestPackage package, string if (platform == "Silverlight" || platform == ".NETPortable" || platform == ".NETStandard" || platform == ".NETCompactFramework") if (skipNonTestAssemblies) - return new SkippedAssemblyFrameworkDriver(assemblyPath, "X"); + return new SkippedAssemblyFrameworkDriver(assemblyPath, package.ID); else return new InvalidAssemblyFrameworkDriver(assemblyPath, package.ID, platform + " test assemblies are not supported by this version of the engine"); diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs index 4fb9b40fa..acc022a58 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2009.cs @@ -64,15 +64,15 @@ public string Load(string testAssemblyPath, IDictionary settings try { - _frameworkController = _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, - CONTROLLER_TYPE, - false, - 0, - null, - new object[] { _testAssemblyPath, idPrefix, settings }, - null, - null).ShouldNotBeNull(); + _frameworkController = _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, + CONTROLLER_TYPE, + false, + 0, + null, + new object[] { _testAssemblyPath, idPrefix, settings }, + null, + null).ShouldNotBeNull(); } catch (BadImageFormatException ex) when (requestedRuntime != null) { @@ -152,10 +152,8 @@ private object CreateObject(string typeName, params object?[]? args) { try { - return _testDomain.CreateInstanceAndUnwrap( - _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; - //return _testDomain.CreateInstanceAndUnwrap( - // _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; + return _testDomain.CreateInstanceAndUnwrap( + _nunitRef.FullName, typeName, false, 0, null, args, null, null)!; } catch (TargetInvocationException ex) { diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs index 8d5778764..535388f19 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkApi2018.cs @@ -40,7 +40,6 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi #if NETCOREAPP private TestAssemblyLoadContext? _assemblyLoadContext; - //private Assembly? _testAssembly; private Assembly? _frameworkAssembly; #endif @@ -63,7 +62,7 @@ public string Load(string testAssemblyPath, IDictionary settings log.Info($"Loading {testAssemblyPath} - see separate log file"); _testAssemblyPath = Path.GetFullPath(testAssemblyPath); - var idPrefix = string.IsNullOrEmpty(_driverId) ? "" : _driverId + "-"; + var idPrefix = _driverId + "-"; #if NETFRAMEWORK try @@ -236,10 +235,10 @@ private object ExecuteMethod(MethodInfo? method, params object?[] args) #if NETFRAMEWORK return method.Invoke(_frameworkController, args).ShouldNotBeNull(); #else - using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) - { - return method.Invoke(_frameworkController, args).ShouldNotBeNull(); - } + //using (_assemblyLoadContext.ShouldNotBeNull().EnterContextualReflection()) + //{ + return method.Invoke(_frameworkController, args).ShouldNotBeNull(); + //} #endif } diff --git a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs index 5c082e7cc..b876ba368 100644 --- a/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs +++ b/src/NUnitEngine/nunit.engine.core/Drivers/NUnitFrameworkDriver.cs @@ -108,7 +108,7 @@ public NUnitFrameworkDriver(string id, AssemblyName nunitRef) /// An id prefix that will be passed to the test framework and used as part of the /// test ids created. /// - public string ID { get; } = string.Empty; + public string ID { get; } /// /// Loads the tests in an assembly. diff --git a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs index 5f2fe6dfc..d71e58f14 100644 --- a/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs +++ b/src/NUnitEngine/nunit.engine.core/Runners/TestAgentRunner.cs @@ -136,8 +136,6 @@ public virtual void Unload() { } /// The count of test cases public int CountTestCases(TestFilter filter) { - GetLoadedDriver(); - try { return GetLoadedDriver().CountTestCases(filter.Text);