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); + } } }