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