<PackageReference Include="NUnit" Version="3.0.0-rc-3" />

WorkItem

public abstract class WorkItem
A WorkItem may be an individual test case, a fixture or a higher level grouping of tests. All WorkItems inherit from the abstract WorkItem class, which uses the template pattern to allow derived classes to perform work in whatever way is needed. A WorkItem is created with a particular TestExecutionContext and is responsible for re-establishing that context in the current thread before it begins or resumes execution.
using NUnit.Framework.Interfaces; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace NUnit.Framework.Internal.Execution { public abstract class WorkItem { private static Logger log = InternalTrace.GetLogger("WorkItem"); private WorkItemState _state; private Test _test; private TestExecutionContext _context; private List<ITestAction> _actions = new List<ITestAction>(); public WorkItemState State => _state; public Test Test => _test; public TestExecutionContext Context => _context; public List<ITestAction> Actions => _actions; public bool IsParallelizable { get { ParallelScope parallelScope = ParallelScope.None; if (Test.Properties.ContainsKey("ParallelScope")) { parallelScope = (ParallelScope)Test.Properties.Get("ParallelScope"); if ((parallelScope & ParallelScope.Self) != 0) return true; } else { parallelScope = Context.ParallelScope; if ((parallelScope & ParallelScope.Children) != 0) return true; } if (Test is TestFixture && (parallelScope & ParallelScope.Fixtures) != 0) return true; if (Test is TestAssembly && parallelScope != 0) return true; return false; } } public TestResult Result { get; set; } internal ApartmentState TargetApartment { get { if (!Test.Properties.ContainsKey("ApartmentState")) return ApartmentState.Unknown; return (ApartmentState)_test.Properties.Get("ApartmentState"); } } public event EventHandler Completed; public static WorkItem CreateWorkItem(ITest test, ITestFilter filter) { TestSuite testSuite = test as TestSuite; if (testSuite != null) return new CompositeWorkItem(testSuite, filter); return new SimpleWorkItem((TestMethod)test, filter); } public WorkItem(Test test) { _test = test; Result = test.MakeTestResult(); _state = WorkItemState.Ready; } public void InitializeContext(TestExecutionContext context) { Guard.OperationValid(_context == null, "The context has already been initialized"); _context = context; if (Test is TestAssembly) _actions.AddRange(ActionsHelper.GetActionsFromAttributeProvider(((TestAssembly)Test).Assembly)); else if (Test is ParameterizedMethodSuite) { _actions.AddRange(ActionsHelper.GetActionsFromAttributeProvider(Test.Method.MethodInfo)); } else if (Test.TypeInfo != null) { _actions.AddRange(ActionsHelper.GetActionsFromTypesAttributes(Test.TypeInfo.Type)); } } public virtual void Execute() { int num = _context.TestCaseTimeout; if (Test.Properties.ContainsKey("Timeout")) num = (int)Test.Properties.Get("Timeout"); ApartmentState apartmentState = Thread.CurrentThread.GetApartmentState(); if (Test.RequiresThread || (Test is TestMethod && num > 0) || (apartmentState != TargetApartment && TargetApartment != ApartmentState.Unknown)) RunTestOnOwnThread(num, TargetApartment); else RunTest(); } private void RunTestOnOwnThread(int timeout, ApartmentState apartment) { string str = Test.RequiresThread ? "has RequiresThreadAttribute." : ((timeout > 0) ? "has Timeout value set." : "requires a different apartment."); log.Debug("Running test on own thread because it " + str); Thread thread = new Thread(RunTest); thread.SetApartmentState(apartment); thread.CurrentCulture = Context.CurrentCulture; thread.CurrentUICulture = Context.CurrentUICulture; thread.Start(); if (!Test.IsAsynchronous || timeout > 0) { if (timeout <= 0) timeout = -1; thread.Join(timeout); if (thread.IsAlive) { log.Debug("Killing thread {0}, which exceeded timeout", thread.ManagedThreadId); ThreadUtility.Kill(thread); thread.Join(); log.Debug("Changing result from {0} to Timeout Failure", Result.ResultState); Result.SetResult(ResultState.Failure, $"""{timeout}"""); WorkItemComplete(); } } } private void RunTest() { _context.CurrentTest = Test; _context.CurrentResult = Result; _context.Listener.TestStarted(Test); _context.StartTime = DateTime.UtcNow; _context.StartTicks = Stopwatch.GetTimestamp(); _context.EstablishExecutionEnvironment(); _state = WorkItemState.Running; try { PerformWork(); } catch (ThreadAbortException) { } } protected abstract void PerformWork(); protected void WorkItemComplete() { _state = WorkItemState.Complete; Result.StartTime = Context.StartTime; Result.EndTime = DateTime.UtcNow; long num = Stopwatch.GetTimestamp() - Context.StartTicks; double duration = (double)num / (double)Stopwatch.Frequency; Result.Duration = duration; Result.AssertCount += Context.AssertCount; _context.Listener.TestFinished(Result); if (this.Completed != null) this.Completed(this, EventArgs.Empty); } } }