<PackageReference Include="NUnit" Version="3.6.0" />

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 { [Flags] private enum OwnThreadReason { NotNeeded = 0, RequiresThread = 1, Timeout = 2, DifferentApartment = 4 } private static Logger log = InternalTrace.GetLogger("WorkItem"); private Thread thread; private object threadLock = new object(); public WorkItemState State { get; set; } public Test Test { get; set; } public TestExecutionContext Context { get; set; } public string WorkerId { get; set; } public List<ITestAction> Actions { get; set; } 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; set; } private ApartmentState CurrentApartment { get; set; } 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; Actions = new List<ITestAction>(); TargetApartment = (Test.Properties.ContainsKey("ApartmentState") ? ((ApartmentState)Test.Properties.Get("ApartmentState")) : ApartmentState.Unknown); } 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"); OwnThreadReason ownThreadReason = OwnThreadReason.NotNeeded; if (Test.RequiresThread) ownThreadReason |= OwnThreadReason.RequiresThread; if (num > 0 && Test is TestMethod) ownThreadReason |= OwnThreadReason.Timeout; CurrentApartment = Thread.CurrentThread.GetApartmentState(); if (CurrentApartment != TargetApartment && TargetApartment != ApartmentState.Unknown) ownThreadReason |= OwnThreadReason.DifferentApartment; if (ownThreadReason == OwnThreadReason.NotNeeded) RunTest(); else if (Context.IsSingleThreaded) { string message = "Test is not runnable in single-threaded context. " + ownThreadReason; log.Error(message); Result.SetResult(ResultState.NotRunnable, message); WorkItemComplete(); } else { log.Debug("Running test on own thread. " + ownThreadReason); ApartmentState apartment = ((ownThreadReason | OwnThreadReason.DifferentApartment) != 0) ? TargetApartment : CurrentApartment; RunTestOnOwnThread(num, apartment); } } private void RunTestOnOwnThread(int timeout, ApartmentState apartment) { thread = new Thread(RunTest); thread.SetApartmentState(apartment); RunThread(timeout); } private void RunThread(int timeout) { this.thread.CurrentCulture = Context.CurrentCulture; this.thread.CurrentUICulture = Context.CurrentUICulture; this.thread.Start(); if (timeout <= 0) timeout = -1; if (!this.thread.Join(timeout)) { if (Debugger.IsAttached) this.thread.Join(); else { Thread thread = default(Thread); lock (threadLock) { if (this.thread == null) return; thread = this.thread; this.thread = null; } if (Context.ExecutionStatus != TestExecutionStatus.AbortRequested) { 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.WorkerId = WorkerId; Context.EstablishExecutionEnvironment(); State = WorkItemState.Running; PerformWork(); } public virtual void Cancel(bool force) { if (Context != null) Context.ExecutionStatus = ((!force) ? TestExecutionStatus.StopRequested : TestExecutionStatus.AbortRequested); if (force) { Thread thread = default(Thread); lock (threadLock) { if (this.thread == null) return; thread = this.thread; this.thread = null; } if (!thread.Join(0)) { log.Debug("Killing thread {0} for cancel", thread.ManagedThreadId); ThreadUtility.Kill(thread); thread.Join(); log.Debug("Changing result from {0} to Cancelled", Result.ResultState); Result.SetResult(ResultState.Cancelled, "Cancelled by user"); WorkItemComplete(); } } } protected abstract void PerformWork(); protected void WorkItemComplete() { State = WorkItemState.Complete; Result.StartTime = Context.StartTime; Result.EndTime = DateTime.UtcNow; double duration = (double)(Stopwatch.GetTimestamp() - Context.StartTicks) / (double)Stopwatch.Frequency; Result.Duration = duration; Result.AssertCount += Context.AssertCount; Context.Listener.TestFinished(Result); if (this.Completed != null) this.Completed(this, EventArgs.Empty); Context.TestObject = null; Test.Fixture = null; } } }