Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling extension of events #4698

Merged
merged 7 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 32 additions & 9 deletions src/NUnitFramework/framework/Internal/Execution/EventPump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,32 @@ public enum EventPumpState
}

/// <summary>
/// EventPump pulls events out of an EventQueue and sends
/// them to a listener. It is used to send events back to
/// EventPump pulls Event instances out of an EventQueue and sends
/// them to a ITestListener. It is used to send these events back to
/// the client without using the CallContext of the test
/// runner thread.
/// </summary>
public class EventPump : IDisposable
public sealed class EventPump : EventPump<Event, ITestListener>, IDisposable
{
/// <summary>
/// Constructor for standard EventPump
/// </summary>
/// <param name="eventListener">The EventListener to receive events</param>
/// <param name="events">The event queue to pull events from</param>
public EventPump(ITestListener eventListener, EventQueue<Event> events)
: base(eventListener, events, "Standard")
{
}
}

/// <summary>
/// EventPump base class pulls events of any type out of an EventQueue and sends
/// them to any listener. It is used to send events back to
/// the client without using the CallContext of the test
/// runner thread.
/// </summary>
public abstract class EventPump<TEvent, TListener> : IDisposable
where TEvent : IEvent<TListener>
{
private static readonly Logger Log = InternalTrace.GetLogger("EventPump");

Expand All @@ -43,12 +63,12 @@ public class EventPump : IDisposable
/// <summary>
/// The downstream listener to which we send events
/// </summary>
private readonly ITestListener _eventListener;
private readonly TListener _eventListener;

/// <summary>
/// The queue that holds our events
/// </summary>
private readonly EventQueue _events;
private readonly EventQueue<TEvent> _events;

/// <summary>
/// Thread to do the pumping
Expand All @@ -63,15 +83,18 @@ public class EventPump : IDisposable
#endregion

#region Constructor

/// <summary>
/// Constructor
/// </summary>
/// <param name="eventListener">The EventListener to receive events</param>
/// <param name="events">The event queue to pull events from</param>
public EventPump(ITestListener eventListener, EventQueue events)
/// <param name="name">Name of the thread and pump</param>
protected EventPump(TListener eventListener, EventQueue<TEvent> events, string name = "Standard")
{
_eventListener = eventListener;
_events = events;
Name = name;
}

#endregion
Expand Down Expand Up @@ -111,7 +134,7 @@ public void Start()
{
_pumpThread = new Thread(PumpThreadProc)
{
Name = "EventPumpThread" + Name,
Name = $"{Name}EventPumpThread",
Priority = ThreadPriority.Highest
};

Expand Down Expand Up @@ -142,14 +165,14 @@ public void Stop()
/// </summary>
private void PumpThreadProc()
{
Log.Debug("Starting EventPump");
Log.Debug($"Starting {Name}");

//ITestListener hostListeners = CoreExtensions.Host.Listeners;
try
{
while (true)
{
Event? e = _events.Dequeue(PumpState == EventPumpState.Pumping);
var e = _events.Dequeue(PumpState == EventPumpState.Pumping);
if (e is null)
break;
try
Expand Down
42 changes: 32 additions & 10 deletions src/NUnitFramework/framework/Internal/Execution/EventQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,29 @@

namespace NUnit.Framework.Internal.Execution
{
/// <summary>
/// Interface for ALL event types that can be queued for processing.
/// </summary>
/// <typeparam name="TListener"></typeparam>
public interface IEvent<in TListener>
{
/// <summary>
/// The Send method is implemented by derived classes to send the event to the specified listener.
/// </summary>
/// <param name="listener">The listener.</param>
void Send(TListener listener);
OsirisTerje marked this conversation as resolved.
Show resolved Hide resolved
}

#region Individual Event Classes

/// <summary>
/// NUnit.Core.Event is the abstract base for all stored events.
/// NUnit.Core.Event is the abstract base for all stored standard events.
/// An Event is the stored representation of a call to the
/// ITestListener interface and is used to record such calls
/// or to queue them for forwarding on another thread or at
/// a later time.
/// </summary>
public abstract class Event
public abstract class Event : IEvent<ITestListener>
{
/// <summary>
/// The Send method is implemented by derived classes to send the event to the specified listener.
Expand Down Expand Up @@ -134,16 +147,25 @@ public override void Send(ITestListener listener)
#endregion

/// <summary>
/// Implements a queue of work items each of which
/// Implements a queue of work items for the Event type each of which
/// is queued as a WaitCallback.
/// </summary>
public sealed class EventQueue : EventQueue<Event>
{
}

/// <summary>
/// Implements a template for a queue of work items each of which
/// is queued as a WaitCallback.
/// It can handle any event types.
/// </summary>
public class EventQueue
public abstract class EventQueue<T>
{
private const int SpinCount = 5;

// static readonly Logger log = InternalTrace.GetLogger("EventQueue");

private readonly ConcurrentQueue<Event> _queue = new();
private readonly ConcurrentQueue<T> _queue = new();

/* This event is used solely for the purpose of having an optimized sleep cycle when
* we have to wait on an external event (Add or Remove for instance)
Expand Down Expand Up @@ -171,7 +193,7 @@ public class EventQueue
/// Enqueues the specified event
/// </summary>
/// <param name="e">The event to enqueue.</param>
public void Enqueue(Event e)
public void Enqueue(T e)
{
do
{
Expand Down Expand Up @@ -216,7 +238,7 @@ public void Enqueue(Event e)
/// </item>
/// </list>
/// </returns>
public Event? Dequeue(bool blockWhenEmpty)
public T? Dequeue(bool blockWhenEmpty)
{
SpinWait sw = new SpinWait();

Expand All @@ -229,7 +251,7 @@ public void Enqueue(Event e)
if (cachedRemoveId == cachedAddId)
{
if (!blockWhenEmpty || _stopped != 0)
return null;
return default(T);

// Spin a few times to see if something changes
if (sw.Count <= SpinCount)
Expand Down Expand Up @@ -261,11 +283,11 @@ public void Enqueue(Event e)
continue;

// Dequeue our work item
Event? e;
T? e;
while (!_queue.TryDequeue(out e))
{
if (!blockWhenEmpty || _stopped != 0)
return null;
return default(T);
}

return e;
Expand Down