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

OccurredEvent ordering on monitored object is now done via thread-safe counter #1773

Merged
merged 9 commits into from Jan 17, 2022
6 changes: 4 additions & 2 deletions Src/FluentAssertions/Events/EventMonitor.cs
Expand Up @@ -30,6 +30,8 @@ public EventMonitor(object eventSource, Func<DateTime> utcNow)

public T Subject => (T)subject.Target;

private EventRaisedOrder EventRaisedOrder { get; } = new();

public EventMetadata[] MonitoredEvents
{
get
Expand All @@ -49,7 +51,7 @@ public OccurredEvent[] OccurredEvents
from eventName in recorderMap.Keys
let recording = GetRecordingFor(eventName)
from @event in recording
orderby @event.TimestampUtc
orderby @event.RaisedOrderIndex
select @event;

return query.ToArray();
Expand Down Expand Up @@ -125,7 +127,7 @@ private void AttachEventHandler(EventInfo eventInfo, Func<DateTime> utcNow)
{
if (!recorderMap.TryGetValue(eventInfo.Name, out _))
{
var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow);
var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow, EventRaisedOrder);
if (recorderMap.TryAdd(eventInfo.Name, recorder))
{
recorder.Attach(subject, eventInfo);
Expand Down
20 changes: 20 additions & 0 deletions Src/FluentAssertions/Events/EventRaisedOrder.cs
@@ -0,0 +1,20 @@
using System.Threading;

namespace FluentAssertions.Events
{
/// <summary>
/// Tracks the order of events raised by <see cref="FluentAssertions.Events.EventMonitor{T}"/>.
/// </summary>
internal sealed class EventRaisedOrder
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
private int orderIndex = -1;

/// <summary>
/// Increments the current order index.
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public int Increment()
{
return Interlocked.Increment(ref orderIndex);
}
}
}
11 changes: 8 additions & 3 deletions Src/FluentAssertions/Events/EventRecorder.cs
Expand Up @@ -25,11 +25,13 @@ internal sealed class EventRecorder : IEventRecording, IDisposable
/// <param name="eventRaiser">The object events are recorded from</param>
/// <param name="eventName">The name of the event that's recorded</param>
/// <param name="utcNow">A delegate to get the current date and time in UTC format.</param>
public EventRecorder(object eventRaiser, string eventName, Func<DateTime> utcNow)
/// <param name="eventRaisedOrder">Class used to track order of raised events.</param>
public EventRecorder(object eventRaiser, string eventName, Func<DateTime> utcNow, EventRaisedOrder eventRaisedOrder)
{
this.utcNow = utcNow;
EventObject = eventRaiser;
EventName = eventName;
EventRaisedOrder = eventRaisedOrder;
}

/// <summary>
Expand All @@ -40,6 +42,8 @@ public EventRecorder(object eventRaiser, string eventName, Func<DateTime> utcNow
/// <inheritdoc />
public string EventName { get; }

private EventRaisedOrder EventRaisedOrder { get; }
MullerWasHere marked this conversation as resolved.
Show resolved Hide resolved

public Type EventHandlerType { get; private set; }

public void Attach(WeakReference subject, EventInfo eventInfo)
Expand Down Expand Up @@ -77,7 +81,7 @@ public void RecordEvent(params object[] parameters)
{
lock (lockable)
{
raisedEvents.Add(new RecordedEvent(utcNow(), parameters));
raisedEvents.Add(new RecordedEvent(utcNow(), EventRaisedOrder.Increment(), parameters));
}
}

Expand Down Expand Up @@ -105,7 +109,8 @@ public IEnumerator<OccurredEvent> GetEnumerator()
{
EventName = EventName,
Parameters = @event.Parameters,
TimestampUtc = @event.TimestampUtc
TimestampUtc = @event.TimestampUtc,
RaisedOrderIndex = @event.RaisedOrderIndex
};
}
}
Expand Down
5 changes: 5 additions & 0 deletions Src/FluentAssertions/Events/OccurredEvent.cs
Expand Up @@ -21,5 +21,10 @@ public class OccurredEvent
/// The exact date and time of the occurrence in <see cref="DateTimeKind.Local"/>.
/// </summary>
public DateTime TimestampUtc { get; set; }

/// <summary>
/// The order in which this event was raised on the monitored object.
/// </summary>
public int RaisedOrderIndex { get; set; }
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
}
}
8 changes: 7 additions & 1 deletion Src/FluentAssertions/Events/RecordedEvent.cs
Expand Up @@ -12,10 +12,11 @@ internal class RecordedEvent
/// <summary>
/// Default constructor stores the parameters the event was raised with
/// </summary>
public RecordedEvent(DateTime utcNow, params object[] parameters)
public RecordedEvent(DateTime utcNow, int raisedOrderIndex, params object[] parameters)
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
Parameters = parameters;
TimestampUtc = utcNow;
RaisedOrderIndex = raisedOrderIndex;
}

/// <summary>
Expand All @@ -27,5 +28,10 @@ public RecordedEvent(DateTime utcNow, params object[] parameters)
/// Parameters for the event
/// </summary>
public object[] Parameters { get; }

/// <summary>
/// The order in which this event was invoked on the monitored object.
/// </summary>
public int RaisedOrderIndex { get; }
}
}
Expand Up @@ -1217,6 +1217,7 @@ namespace FluentAssertions.Events
public OccurredEvent() { }
public string EventName { get; set; }
public object[] Parameters { get; set; }
public int RaisedOrderIndex { get; set; }
public System.DateTime TimestampUtc { get; set; }
}
}
Expand Down
Expand Up @@ -1217,6 +1217,7 @@ namespace FluentAssertions.Events
public OccurredEvent() { }
public string EventName { get; set; }
public object[] Parameters { get; set; }
public int RaisedOrderIndex { get; set; }
public System.DateTime TimestampUtc { get; set; }
}
}
Expand Down
Expand Up @@ -1217,6 +1217,7 @@ namespace FluentAssertions.Events
public OccurredEvent() { }
public string EventName { get; set; }
public object[] Parameters { get; set; }
public int RaisedOrderIndex { get; set; }
public System.DateTime TimestampUtc { get; set; }
}
}
Expand Down
Expand Up @@ -1217,6 +1217,7 @@ namespace FluentAssertions.Events
public OccurredEvent() { }
public string EventName { get; set; }
public object[] Parameters { get; set; }
public int RaisedOrderIndex { get; set; }
public System.DateTime TimestampUtc { get; set; }
}
}
Expand Down
43 changes: 43 additions & 0 deletions Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs
Expand Up @@ -411,6 +411,28 @@ public void When_constraints_are_specified_it_should_filter_the_events_based_on_
.Which.PropertyName.Should().Be("Boo");
}

[Fact]
public void When_events_are_raised_regardless_of_time_tick_it_should_return_by_invokation_order()
{
// Arrange
var observable = new TestEventRaisingInOrder();
var utcNow = 11.January(2022).At(12, 00).AsUtc();
using var monitor = observable.Monitor(() => utcNow);

// Act
observable.RaiseAllEvents();

// Assert
monitor.OccurredEvents[0].EventName.Should().Be(nameof(TestEventRaisingInOrder.InterfaceEvent));
monitor.OccurredEvents[0].RaisedOrderIndex.Should().Be(0);

monitor.OccurredEvents[1].EventName.Should().Be(nameof(TestEventRaisingInOrder.Interface2Event));
monitor.OccurredEvents[1].RaisedOrderIndex.Should().Be(1);

monitor.OccurredEvents[2].EventName.Should().Be(nameof(TestEventRaisingInOrder.Interface3Event));
monitor.OccurredEvents[2].RaisedOrderIndex.Should().Be(2);
}

#endregion

#region Should(Not)RaisePropertyChanged events
Expand Down Expand Up @@ -817,6 +839,22 @@ public void RaiseBothEvents()
}
}

private class TestEventRaisingInOrder : IEventRaisingInterface, IEventRaisingInterface2, IEventRaisingInterface3
{
public event EventHandler Interface3Event;

public event EventHandler Interface2Event;

public event EventHandler InterfaceEvent;

public void RaiseAllEvents()
{
InterfaceEvent?.Invoke(this, EventArgs.Empty);
Interface2Event?.Invoke(this, EventArgs.Empty);
Interface3Event?.Invoke(this, EventArgs.Empty);
}
}

public interface IEventRaisingInterface
{
event EventHandler InterfaceEvent;
Expand All @@ -827,6 +865,11 @@ public interface IEventRaisingInterface2
event EventHandler Interface2Event;
}

public interface IEventRaisingInterface3
{
event EventHandler Interface3Event;
}

public interface IInheritsEventRaisingInterface : IEventRaisingInterface
{
}
Expand Down
2 changes: 1 addition & 1 deletion docs/_pages/releases.md
Expand Up @@ -17,7 +17,7 @@ sidebar:
* `ContainItemsAssignableTo` now expects at least one item assignable to `T` - [#1765](https://github.com/fluentassertions/fluentassertions/pull/1765)
* Querying methods on classes, e.g. `typeof(MyController).Methods()`, now also includes static methods - [#1740](https://github.com/fluentassertions/fluentassertions/pull/1740)
* Variable name is not captured after await assertion - [#1770](https://github.com/fluentassertions/fluentassertions/pull/1770)

* `OccurredEvent` ordering on monitored object is now done via thread-safe counter - [#1723](https://github.com/fluentassertions/fluentassertions/pull/1773)
MullerWasHere marked this conversation as resolved.
Show resolved Hide resolved
## 6.3.0

### What's New
Expand Down