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

SingleThreadedTestSynchronizationContext

using NUnit.Framework.Interfaces; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; namespace NUnit.Framework.Internal { [NullableContext(1)] [Nullable(0)] internal sealed class SingleThreadedTestSynchronizationContext : SynchronizationContext, IDisposable { [NullableContext(0)] private enum Status { NotStarted, Running, ShuttingDown, ShutDown } [NullableContext(2)] [Nullable(0)] private struct ScheduledWork { [Nullable(1)] private readonly SendOrPostCallback _callback; private readonly object _state; private readonly ManualResetEventSlim _finished; public ScheduledWork([Nullable(1)] SendOrPostCallback callback, object state, ManualResetEventSlim finished) { _callback = callback; _state = state; _finished = finished; } public void Execute() { _callback(_state); _finished?.Set(); } } private const string ShutdownTimeoutMessage = "Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete."; private readonly TimeSpan _shutdownTimeout; private readonly Queue<ScheduledWork> _queue = new Queue<ScheduledWork>(); private Status _status; [Nullable(2)] private Stopwatch _timeSinceShutdown; public SingleThreadedTestSynchronizationContext(TimeSpan shutdownTimeout) { _shutdownTimeout = shutdownTimeout; } public override void Post(SendOrPostCallback d, [Nullable(2)] object state) { Guard.ArgumentNotNull(d, "d"); AddWork(new ScheduledWork(d, state, null)); } public override void Send(SendOrPostCallback d, [Nullable(2)] object state) { Guard.ArgumentNotNull(d, "d"); if (SynchronizationContext.Current == this) d(state); else { using (ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim()) { AddWork(new ScheduledWork(d, state, manualResetEventSlim)); manualResetEventSlim.Wait(); } } } private void AddWork(ScheduledWork work) { lock (_queue) { switch (_status) { case Status.ShuttingDown: if (_timeSinceShutdown.Elapsed < _shutdownTimeout) break; goto case Status.ShutDown; case Status.ShutDown: throw ErrorAndGetExceptionForShutdownTimeout(); } _queue.Enqueue(work); Monitor.Pulse(_queue); } } public void ShutDown() { lock (_queue) { Status status = _status; if ((uint)(status - 2) > 1) { _timeSinceShutdown = Stopwatch.StartNew(); _status = Status.ShuttingDown; Monitor.Pulse(_queue); } } } public void Run() { lock (_queue) { Status status = _status; if (status == Status.Running) throw new InvalidOperationException("SingleThreadedTestSynchronizationContext.Run may not be reentered."); if ((uint)(status - 2) <= 1) throw new InvalidOperationException("This SingleThreadedTestSynchronizationContext has been shut down."); _status = Status.Running; } ScheduledWork scheduledWork; while (TryTake(out scheduledWork)) { scheduledWork.Execute(); } } private bool TryTake(out ScheduledWork scheduledWork) { lock (_queue) { while (_queue.Count == 0) { if (_status == Status.ShuttingDown) { _status = Status.ShutDown; scheduledWork = default(ScheduledWork); return false; } Monitor.Wait(_queue); } if (_status == Status.ShuttingDown && _timeSinceShutdown.Elapsed > _shutdownTimeout) { _status = Status.ShutDown; throw ErrorAndGetExceptionForShutdownTimeout(); } scheduledWork = _queue.Dequeue(); } return true; } private static Exception ErrorAndGetExceptionForShutdownTimeout() { TestExecutionContext.CurrentContext?.CurrentResult.RecordAssertion(AssertionStatus.Error, "Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete."); return new InvalidOperationException("Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete."); } public void Dispose() { ShutDown(); } } }