1 // ***********************************************************************
2 // Copyright (c) 2007-2016 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
31 using System.Collections;
32 using System.Collections.Concurrent;
33 using System.Globalization;
34 //using System.Runtime.Serialization;
35 using System.Threading;
36 #if NET_2_0 || NET_3_5 || NETCF
37 using ManualResetEventSlim = System.Threading.ManualResetEvent;
39 using NUnit.Framework.Interfaces;
41 namespace NUnit.Framework.Internal.Execution
44 #region Individual Event Classes
47 /// NUnit.Core.Event is the abstract base for all stored events.
48 /// An Event is the stored representation of a call to the
49 /// ITestListener interface and is used to record such calls
50 /// or to queue them for forwarding on another thread or at
53 public abstract class Event
56 /// The Send method is implemented by derived classes to send the event to the specified listener.
58 /// <param name="listener">The listener.</param>
59 public abstract void Send(ITestListener listener);
63 /// TestStartedEvent holds information needed to call the TestStarted method.
65 public class TestStartedEvent : Event
67 private readonly ITest _test;
70 /// Initializes a new instance of the <see cref="TestStartedEvent"/> class.
72 /// <param name="test">The test.</param>
73 public TestStartedEvent(ITest test)
79 /// Calls TestStarted on the specified listener.
81 /// <param name="listener">The listener.</param>
82 public override void Send(ITestListener listener)
84 listener.TestStarted(_test);
89 /// TestFinishedEvent holds information needed to call the TestFinished method.
91 public class TestFinishedEvent : Event
93 private readonly ITestResult _result;
96 /// Initializes a new instance of the <see cref="TestFinishedEvent"/> class.
98 /// <param name="result">The result.</param>
99 public TestFinishedEvent(ITestResult result)
105 /// Calls TestFinished on the specified listener.
107 /// <param name="listener">The listener.</param>
108 public override void Send(ITestListener listener)
110 listener.TestFinished(_result);
115 /// TestOutputEvent holds information needed to call the TestOutput method.
117 public class TestOutputEvent : Event
119 private readonly TestOutput _output;
122 /// Initializes a new instance of the <see cref="TestOutputEvent"/> class.
124 /// <param name="output">The output object.</param>
125 public TestOutputEvent(TestOutput output)
131 /// Calls TestOutput on the specified listener.
133 /// <param name="listener">The listener.</param>
134 public override void Send(ITestListener listener)
136 listener.TestOutput(_output);
143 /// Implements a queue of work items each of which
144 /// is queued as a WaitCallback.
146 public class EventQueue
148 private const int spinCount = 5;
150 // static readonly Logger log = InternalTrace.GetLogger("EventQueue");
152 private readonly ConcurrentQueue<Event> _queue = new ConcurrentQueue<Event>();
154 /* This event is used solely for the purpose of having an optimized sleep cycle when
155 * we have to wait on an external event (Add or Remove for instance)
157 private readonly ManualResetEventSlim _mreAdd = new ManualResetEventSlim(false);
159 /* The whole idea is to use these two values in a transactional
160 * way to track and manage the actual data inside the underlying lock-free collection
161 * instead of directly working with it or using external locking.
163 * They are manipulated with CAS and are guaranteed to increase over time and use
164 * of the instance thus preventing ABA problems.
166 private int _addId = int.MinValue;
167 private int _removeId = int.MinValue;
169 private int _stopped;
172 /// Gets the count of items in the queue.
183 /// Enqueues the specified event
185 /// <param name="e">The event to enqueue.</param>
186 public void Enqueue(Event e)
190 int cachedAddId = _addId;
192 // Validate that we have are the current enqueuer
193 if (Interlocked.CompareExchange(ref _addId, cachedAddId + 1, cachedAddId) != cachedAddId)
196 // Add to the collection
199 // Wake up threads that may have been sleeping
205 Thread.Sleep(1); // give EventPump thread a chance to process the event
209 /// Removes the first element from the queue and returns it (or <c>null</c>).
211 /// <param name="blockWhenEmpty">
212 /// If <c>true</c> and the queue is empty, the calling thread is blocked until
213 /// either an element is enqueued, or <see cref="Stop"/> is called.
216 /// <list type="bullet">
218 /// <term>If the queue not empty</term>
219 /// <description>the first element.</description>
222 /// <term>otherwise, if <paramref name="blockWhenEmpty"/>==<c>false</c>
223 /// or <see cref="Stop"/> has been called</term>
224 /// <description><c>null</c>.</description>
228 public Event Dequeue(bool blockWhenEmpty)
230 SpinWait sw = new SpinWait();
234 int cachedRemoveId = _removeId;
235 int cachedAddId = _addId;
238 if (cachedRemoveId == cachedAddId)
240 if (!blockWhenEmpty || _stopped != 0)
243 // Spin a few times to see if something changes
244 if (sw.Count <= spinCount)
250 // Reset to wait for an enqueue
253 // Recheck for an enqueue to avoid a Wait
254 if (cachedRemoveId != _removeId || cachedAddId != _addId)
256 // Queue is not empty, set the event
261 // Wait for something to happen
268 // Validate that we are the current dequeuer
269 if (Interlocked.CompareExchange(ref _removeId, cachedRemoveId + 1, cachedRemoveId) != cachedRemoveId)
273 // Dequeue our work item
275 while (!_queue.TryDequeue (out e))
277 if (!blockWhenEmpty || _stopped != 0)
286 /// Stop processing of the queue
290 if (Interlocked.CompareExchange(ref _stopped, 1, 0) == 0)