NUnitTestAssemblyRunner
Implementation of ITestAssemblyRunner
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Abstractions;
using NUnit.Framework.Internal.Execution;
using NUnit.Framework.Internal.Extensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;
namespace NUnit.Framework.Api
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public class NUnitTestAssemblyRunner : ITestAssemblyRunner
{
private static readonly Logger Log = InternalTrace.GetLogger("DefaultTestAssemblyRunner");
private readonly ITestAssemblyBuilder _builder;
private readonly ManualResetEventSlim _runComplete = new ManualResetEventSlim();
[System.Runtime.CompilerServices.Nullable(2)]
private TextWriter _savedOut;
[System.Runtime.CompilerServices.Nullable(2)]
private TextWriter _savedErr;
[System.Runtime.CompilerServices.Nullable(2)]
private EventPump _pump;
public static int DefaultLevelOfParallelism => Math.Max(Environment.ProcessorCount, 2);
[System.Runtime.CompilerServices.Nullable(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
public ITest LoadedTest {
[System.Runtime.CompilerServices.NullableContext(2)]
get;
[System.Runtime.CompilerServices.NullableContext(2)]
private set;
}
[System.Runtime.CompilerServices.Nullable(2)]
public ITestResult Result {
[System.Runtime.CompilerServices.NullableContext(2)]
get {
return TopLevelWorkItem?.Result;
}
}
public bool IsTestLoaded => LoadedTest != null;
public bool IsTestRunning {
get {
if (TopLevelWorkItem != null)
return TopLevelWorkItem.State == WorkItemState.Running;
return false;
}
}
public bool IsTestComplete {
get {
if (TopLevelWorkItem != null)
return TopLevelWorkItem.State == WorkItemState.Complete;
return false;
}
}
private IDictionary<string, object> Settings { get; set; }
[System.Runtime.CompilerServices.Nullable(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
private WorkItem TopLevelWorkItem {
[System.Runtime.CompilerServices.NullableContext(2)]
get;
[System.Runtime.CompilerServices.NullableContext(2)]
set;
}
[System.Runtime.CompilerServices.Nullable(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
private TestExecutionContext Context {
[System.Runtime.CompilerServices.NullableContext(2)]
get;
[System.Runtime.CompilerServices.NullableContext(2)]
set;
}
public NUnitTestAssemblyRunner(ITestAssemblyBuilder builder)
{
_builder = builder;
}
public ITest Load(string assemblyNameOrPath, IDictionary<string, object> settings)
{
Settings = settings;
if (settings.TryGetValue("RandomSeed", out object value))
Randomizer.InitialSeed = (int)value;
LoadedTest = WrapInNUnitCallContext(() => _builder.Build(assemblyNameOrPath, settings));
return LoadedTest;
}
public ITest Load(Assembly assembly, IDictionary<string, object> settings)
{
Settings = settings;
if (settings.TryGetValue("RandomSeed", out object value))
Randomizer.InitialSeed = (int)value;
LoadedTest = WrapInNUnitCallContext(() => _builder.Build(assembly, settings));
return LoadedTest;
}
public int CountTestCases(ITestFilter filter)
{
if (LoadedTest == null)
throw new InvalidOperationException("Tests must be loaded before counting test cases.");
return CountTestCases(LoadedTest, filter);
}
public ITest ExploreTests(ITestFilter filter)
{
if (LoadedTest == null)
throw new InvalidOperationException("Tests must be loaded before exploring them.");
if (filter == TestFilter.Empty)
return LoadedTest;
return new TestAssembly((TestAssembly)LoadedTest, filter);
}
public ITestResult Run(ITestListener listener, ITestFilter filter)
{
RunAsync(listener, filter);
WaitForCompletion(-1);
return Result;
}
public void RunAsync(ITestListener listener, ITestFilter filter)
{
Log.Info("Running tests");
if (LoadedTest == null)
throw new InvalidOperationException("Tests must be loaded before running them.");
_runComplete.Reset();
TestExecutionContext context = CreateTestExecutionContext(LoadedTest, listener);
TopLevelWorkItem = WorkItemBuilder.CreateWorkItem(LoadedTest, filter, new DebuggerProxy(), true, true);
if (TopLevelWorkItem == null)
throw new InvalidOperationException("Loaded test didn't result in a WorkItem");
TopLevelWorkItem.InitializeContext(context);
TopLevelWorkItem.Completed += OnRunCompleted;
Context = context;
WrapInNUnitCallContext(delegate {
StartRun(context, TopLevelWorkItem, listener);
});
}
public bool WaitForCompletion(int timeout)
{
return _runComplete.Wait(timeout);
}
public void StopRun(bool force)
{
if (IsTestRunning && Context != null) {
Context.ExecutionStatus = ((!force) ? TestExecutionStatus.StopRequested : TestExecutionStatus.AbortRequested);
Context.Dispatcher.CancelRun(force);
}
}
private void StartRun(TestExecutionContext context, WorkItem topLevelWorkItem, ITestListener listener)
{
_savedOut = Console.Out;
_savedErr = Console.Error;
Console.SetOut(new TextCapture(Console.Out));
Console.SetError(new EventListenerTextWriter("Error", Console.Error));
if (!Settings.TryGetValue("SynchronousEvents", out object value) || !(bool)value) {
QueuingEventListener queuingEventListener = (QueuingEventListener)(context.Listener = new QueuingEventListener());
_pump = new EventPump(listener, queuingEventListener.Events);
_pump.Start();
}
object value2;
if (!Debugger.IsAttached && Settings.TryGetValue("DebugTests", out value2) && (bool)value2)
try {
Debugger.Launch();
} catch (SecurityException) {
topLevelWorkItem.MarkNotRunnable("System.Security.Permissions.UIPermission must be granted in order to launch the debugger.");
return;
} catch (NotImplementedException) {
topLevelWorkItem.MarkNotRunnable("This platform does not support launching the debugger.");
return;
}
context.Dispatcher.Start(topLevelWorkItem);
}
private TestExecutionContext CreateTestExecutionContext(ITest loadedTest, ITestListener listener)
{
TestExecutionContext testExecutionContext = new TestExecutionContext();
if (Settings.TryGetValue("DefaultTimeout", out object value))
testExecutionContext.TestCaseTimeout = (int)value;
if (Settings.TryGetValue("DefaultCulture", out object value2))
testExecutionContext.CurrentCulture = new CultureInfo((string)value2, false);
if (Settings.TryGetValue("DefaultUICulture", out object value3))
testExecutionContext.CurrentUICulture = new CultureInfo((string)value3, false);
if (Settings.TryGetValue("StopOnError", out object value4))
testExecutionContext.StopOnError = (bool)value4;
if (Settings.TryGetValue("ThrowOnEachFailureUnderDebugger", out object value5))
testExecutionContext.ThrowOnEachFailureUnderDebugger = (bool)value5;
testExecutionContext.Listener = listener;
int levelOfParallelism = GetLevelOfParallelism(loadedTest);
if (Settings.TryGetValue("RunOnMainThread", out object value6) && (bool)value6)
testExecutionContext.Dispatcher = new MainThreadWorkItemDispatcher();
else if (levelOfParallelism > 0) {
testExecutionContext.Dispatcher = new ParallelWorkItemDispatcher(levelOfParallelism);
} else {
testExecutionContext.Dispatcher = new SimpleWorkItemDispatcher();
}
return testExecutionContext;
}
private void OnRunCompleted([System.Runtime.CompilerServices.Nullable(2)] object sender, EventArgs e)
{
if (_pump != null)
_pump.Dispose();
if (_savedOut != null)
Console.SetOut(_savedOut);
if (_savedErr != null)
Console.SetError(_savedErr);
_runComplete.Set();
}
private static int CountTestCases(ITest test, ITestFilter filter)
{
if (!test.IsSuite)
return filter.Pass(test) ? 1 : 0;
int num = 0;
IList<ITest> tests = test.Tests;
for (int i = 0; i < tests.Count; i++) {
num += CountTestCases(tests[i], filter);
}
return num;
}
private int GetLevelOfParallelism(ITest loadedTest)
{
if (!Settings.TryGetValue("NumberOfTestWorkers", out object value))
return loadedTest.Properties.TryGet("LevelOfParallelism", DefaultLevelOfParallelism);
return (int)value;
}
protected void WrapInNUnitCallContext(Action action)
{
action();
}
protected T WrapInNUnitCallContext<[System.Runtime.CompilerServices.Nullable(2)] T>(Func<T> function)
{
return function();
}
}
}