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

Allow event monitoring to ignore failing event accessors #1954

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3c3afc9
Removed event monitor builder
Jul 11, 2022
3272e9d
Restructured and extended the event monitoring documentation
Jul 12, 2022
d1f5a6e
updated release notes
Jul 12, 2022
5cec0a5
Merge remote-tracking branch 'root/develop' into develop
Jul 12, 2022
9388b89
Removed unnecessary generic type from EventMonitorOptions
Jul 12, 2022
d66cc9c
fixed build errors due to removing generic from options
Jul 12, 2022
e783b98
Update api checks
Jul 12, 2022
2a5d159
Update Src/FluentAssertions/Events/EventMonitorOptions.cs
apazureck Jul 13, 2022
4cdf1e2
Update Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj
apazureck Jul 13, 2022
2bab3e1
fixed wrong documentation
Jul 13, 2022
d1d149c
Merge branch 'develop' of https://github.com/apazureck/fluentassertio…
Jul 13, 2022
9e185d7
included reviewer feedback
Jul 15, 2022
bf5702c
Update Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs
apazureck Aug 5, 2022
35cb61b
Added more reviewer suggestions.
Aug 5, 2022
7e07ae4
Merge remote-tracking branch 'origin/develop' into develop
Aug 5, 2022
3bd2e5f
Merge remote-tracking branch 'root/develop' into develop
Aug 5, 2022
34810c5
Update Src/FluentAssertions/Events/EventMonitor.cs
apazureck Aug 9, 2022
08fc020
Update Src/FluentAssertions/Events/EventMonitorOptions.cs
apazureck Aug 9, 2022
61ec8a8
Merge branch 'develop' into develop
apazureck Aug 9, 2022
c58bb5e
Merge remote-tracking branch 'FARepo/develop' into develop
apazureck Jul 4, 2023
a1fad9b
fixed erros due to changes in the main repo
apazureck Jul 4, 2023
e4fdc3a
Merge remote-tracking branch 'FARepo/develop' into develop
apazureck Jul 4, 2023
9c8bbb5
- removed the exception when event recorder could not be removed from…
apazureck Jul 4, 2023
19209ff
cleanups
jnyrup Jul 14, 2023
6f6e555
fixup docs
jnyrup Jul 14, 2023
729a048
Merge remote-tracking branch 'FARepo/develop' into develop
apazureck Aug 25, 2023
e623306
Merge remote-tracking branch 'origin/develop' into develop
apazureck Aug 25, 2023
d9bd625
Fixed Qodana complaints
apazureck Aug 25, 2023
09b35f9
Update docs/_pages/eventmonitoring.md
apazureck Aug 25, 2023
db55f59
Fixed more Qodana issues
apazureck Aug 26, 2023
6759a10
Merge remote-tracking branch 'origin/develop' into develop
apazureck Aug 26, 2023
d894f0f
Removed boolean parameter and added method for recording events with …
apazureck Aug 26, 2023
1b0eaaf
updated api tests for new RecordEventsWithBrokenAccessor method.
apazureck Aug 26, 2023
0849e64
Update docs/_pages/eventmonitoring.md
apazureck Aug 30, 2023
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
26 changes: 25 additions & 1 deletion Src/FluentAssertions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,31 @@ public static TaskCompletionSourceAssertions<T> Should<T>(this TaskCompletionSou
/// <exception cref="ArgumentNullException"><paramref name="eventSource"/> is <see langword="null"/>.</exception>
public static IMonitor<T> Monitor<T>(this T eventSource, Func<DateTime> utcNow = null)
{
return new EventMonitor<T>(eventSource, utcNow ?? (() => DateTime.UtcNow));
var options = new EventMonitorOptions();

if (utcNow is not null)
{
options.ConfigureTimestampProvider(utcNow);
}

return new EventMonitor<T>(eventSource, options);
}
Comment on lines 918 to +928
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're open to change the existing API, we could remove Func<DateTime> utcNow = null, such that this becomes.

public static IMonitor<T> Monitor<T>(this T eventSource) =>
    new EventMonitor<T>(eventSource, new EventMonitorOptions());

For testing we still have the internal ConfigureTimestampProvider.

@dennisdoomen What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing we still have the internal ConfigureTimestampProvider.

What's that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventMonitorOptions.ConfigureTimestampProvider.

In #625 the Func<DateTime> utcNow = null was added, but I suspect it wasn't meant for public use, but merely a necessity for testing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, you're right. That would no longer be necessary.

Copy link
Contributor Author

@apazureck apazureck Aug 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a workflow for obsolete methods? Should it be tagged obsolete first with the remark it will be removed in the next version? Then people have some time, if they are using it for some reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's how we normally do it. But last week, the develop branch is used for the next major version, so we're fine to introduce well-argumented breaking changes in that branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the utcnow. I handed down the options to the eventrecorder, as otherwise the late changed reference on the timestamp provider would not be used. The events are subscribed when the constructor is called. Please check if it is conform to your other code and let me know, if it you like another approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #625 the Func utcNow = null was added, but I suspect it wasn't meant for public use, but merely a necessity for testing.

We should mention this in the docs as well :)


/// <summary>
/// Starts monitoring <paramref name="eventSource"/> for its events.
/// </summary>
/// <param name="eventSource">The object for which to monitor the events.</param>
/// <param name="configureOptions">
/// An optional delegate that can be used to configure the <see cref="EventMonitorOptions"/> used to monitor the events.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="eventSource"/> is <see langword="null"/>.</exception>
public static IMonitor<T> Monitor<T>(this T eventSource, Action<EventMonitorOptions> configureOptions)
{
var options = new EventMonitorOptions();

configureOptions(options);

return new EventMonitor<T>(eventSource, options);
}

#endif
Expand Down
39 changes: 35 additions & 4 deletions Src/FluentAssertions/Events/EventMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ internal sealed class EventMonitor<T> : IMonitor<T>

private readonly ConcurrentDictionary<string, EventRecorder> recorderMap = new();

public EventMonitor(object eventSource, Func<DateTime> utcNow)
public EventMonitor(object eventSource, EventMonitorOptions options)
{
Guard.ThrowIfArgumentIsNull(eventSource, nameof(eventSource), "Cannot monitor the events of a <null> object.");
Guard.ThrowIfArgumentIsNull(options, nameof(options), "Event monitor needs configuration.");

this.options = options;

subject = new WeakReference(eventSource);

Attach(typeof(T), utcNow);
Attach(typeof(T), this.options.TimestampProvider);
}

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

private readonly ThreadSafeSequenceGenerator threadSafeSequenceGenerator = new();
private readonly EventMonitorOptions options;

public EventMetadata[] MonitoredEvents
{
Expand Down Expand Up @@ -117,12 +121,24 @@ public void Dispose()
{
foreach (EventRecorder recorder in recorderMap.Values)
{
recorder.Dispose();
DisposeSafeIfRequested(recorder);
}

recorderMap.Clear();
}

private void DisposeSafeIfRequested(EventRecorder recorder)
{
try
{
recorder.Dispose();
}
catch when (options.ShouldIgnoreEventAccessorExceptions)
{
// ignore
}
}

private void AttachEventHandler(EventInfo eventInfo, Func<DateTime> utcNow)
{
if (!recorderMap.TryGetValue(eventInfo.Name, out _))
Expand All @@ -131,7 +147,22 @@ private void AttachEventHandler(EventInfo eventInfo, Func<DateTime> utcNow)

if (recorderMap.TryAdd(eventInfo.Name, recorder))
{
recorder.Attach(subject, eventInfo);
AttachEventHandler(eventInfo, recorder);
}
}
}

private void AttachEventHandler(EventInfo eventInfo, EventRecorder recorder)
{
try
{
recorder.Attach(subject, eventInfo);
}
catch when (options.ShouldIgnoreEventAccessorExceptions)
{
if (!options.RecordEventsWithBrokenAccessor)
{
recorderMap.TryRemove(eventInfo.Name, out _);
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions Src/FluentAssertions/Events/EventMonitorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;

namespace FluentAssertions.Events;

/// <summary>
/// Settings for the <see cref="EventMonitor{T}"/>.

Check failure on line 6 in Src/FluentAssertions/Events/EventMonitorOptions.cs

View workflow job for this annotation

GitHub Actions / Qodana Scan

Cannot resolve reference in XML comment

Cannot resolve symbol 'EventMonitor'
/// </summary>
public class EventMonitorOptions
{
/// <summary>
/// Will ignore the events, if they throw an exception on any custom event accessor implementation. default: false.
/// </summary>
internal bool ShouldIgnoreEventAccessorExceptions { get; private set; }

/// <summary>
/// This will record the event, even if the event accessor add event accessor threw an exception. To ignore exceptions in the event add accessor, call <see cref="IgnoreEventAccessorExceptions"/> property to true. default: false.
/// </summary>
internal bool RecordEventsWithBrokenAccessor { get; private set; }

/// <summary>
/// Func used to generate the timestamp.
/// </summary>
internal Func<DateTime> TimestampProvider { get; private set; } = () => DateTime.UtcNow;

/// <summary>
/// When called it will ignore event accessor Exceptions.
/// </summary>
/// <param name="recordEventsWithBrokenAccessor">This will record the event, even if the event add event accessor threw an exception. default: false.</param>
/// <returns>The options instance for method stacking.</returns>
public EventMonitorOptions IgnoreEventAccessorExceptions(bool recordEventsWithBrokenAccessor = false)
{
ShouldIgnoreEventAccessorExceptions = true;
RecordEventsWithBrokenAccessor = recordEventsWithBrokenAccessor;
return this;
}

/// <summary>
/// Sets the timestamp provider. By default it is <see cref="DateTime.UtcNow"/>.
/// </summary>
/// <param name="timestampProvider">The timestamp provider.</param>
/// <returns>The options instance for method stacking.</returns>
internal EventMonitorOptions ConfigureTimestampProvider(Func<DateTime> timestampProvider)
{
if (timestampProvider != null)
{
TimestampProvider = timestampProvider;
}

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace FluentAssertions
public static FluentAssertions.Specialized.MemberExecutionTime<T> ExecutionTimeOf<T>(this T subject, System.Linq.Expressions.Expression<System.Action<T>> action, FluentAssertions.Common.StartTimer createTimer = null) { }
public static System.Action Invoking<T>(this T subject, System.Action<T> action) { }
public static System.Func<TResult> Invoking<T, TResult>(this T subject, System.Func<T, TResult> action) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Action<FluentAssertions.Events.EventMonitorOptions> configureOptions) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Func<System.DateTime> utcNow = null) { }
public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { }
[System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" +
Expand Down Expand Up @@ -1270,6 +1271,11 @@ namespace FluentAssertions.Events
public string EventName { get; }
public System.Type HandlerType { get; }
}
public class EventMonitorOptions
{
public EventMonitorOptions() { }
public FluentAssertions.Events.EventMonitorOptions IgnoreEventAccessorExceptions() { }
}
public interface IEventRecording : System.Collections.Generic.IEnumerable<FluentAssertions.Events.OccurredEvent>, System.Collections.IEnumerable
{
System.Type EventHandlerType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace FluentAssertions
public static FluentAssertions.Specialized.MemberExecutionTime<T> ExecutionTimeOf<T>(this T subject, System.Linq.Expressions.Expression<System.Action<T>> action, FluentAssertions.Common.StartTimer createTimer = null) { }
public static System.Action Invoking<T>(this T subject, System.Action<T> action) { }
public static System.Func<TResult> Invoking<T, TResult>(this T subject, System.Func<T, TResult> action) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Action<FluentAssertions.Events.EventMonitorOptions> configureOptions) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Func<System.DateTime> utcNow = null) { }
public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { }
[System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" +
Expand Down Expand Up @@ -1283,6 +1284,11 @@ namespace FluentAssertions.Events
public string EventName { get; }
public System.Type HandlerType { get; }
}
public class EventMonitorOptions
{
public EventMonitorOptions() { }
public FluentAssertions.Events.EventMonitorOptions IgnoreEventAccessorExceptions() { }
}
public interface IEventRecording : System.Collections.Generic.IEnumerable<FluentAssertions.Events.OccurredEvent>, System.Collections.IEnumerable
{
System.Type EventHandlerType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace FluentAssertions
public static FluentAssertions.Specialized.MemberExecutionTime<T> ExecutionTimeOf<T>(this T subject, System.Linq.Expressions.Expression<System.Action<T>> action, FluentAssertions.Common.StartTimer createTimer = null) { }
public static System.Action Invoking<T>(this T subject, System.Action<T> action) { }
public static System.Func<TResult> Invoking<T, TResult>(this T subject, System.Func<T, TResult> action) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Action<FluentAssertions.Events.EventMonitorOptions> configureOptions) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Func<System.DateTime> utcNow = null) { }
public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { }
[System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" +
Expand Down Expand Up @@ -1270,6 +1271,11 @@ namespace FluentAssertions.Events
public string EventName { get; }
public System.Type HandlerType { get; }
}
public class EventMonitorOptions
{
public EventMonitorOptions() { }
public FluentAssertions.Events.EventMonitorOptions IgnoreEventAccessorExceptions() { }
}
public interface IEventRecording : System.Collections.Generic.IEnumerable<FluentAssertions.Events.OccurredEvent>, System.Collections.IEnumerable
{
System.Type EventHandlerType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace FluentAssertions
public static FluentAssertions.Specialized.MemberExecutionTime<T> ExecutionTimeOf<T>(this T subject, System.Linq.Expressions.Expression<System.Action<T>> action, FluentAssertions.Common.StartTimer createTimer = null) { }
public static System.Action Invoking<T>(this T subject, System.Action<T> action) { }
public static System.Func<TResult> Invoking<T, TResult>(this T subject, System.Func<T, TResult> action) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Action<FluentAssertions.Events.EventMonitorOptions> configureOptions) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Func<System.DateTime> utcNow = null) { }
public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { }
[System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" +
Expand Down Expand Up @@ -1270,6 +1271,11 @@ namespace FluentAssertions.Events
public string EventName { get; }
public System.Type HandlerType { get; }
}
public class EventMonitorOptions
{
public EventMonitorOptions() { }
public FluentAssertions.Events.EventMonitorOptions IgnoreEventAccessorExceptions() { }
}
public interface IEventRecording : System.Collections.Generic.IEnumerable<FluentAssertions.Events.OccurredEvent>, System.Collections.IEnumerable
{
System.Type EventHandlerType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace FluentAssertions
public static FluentAssertions.Specialized.MemberExecutionTime<T> ExecutionTimeOf<T>(this T subject, System.Linq.Expressions.Expression<System.Action<T>> action, FluentAssertions.Common.StartTimer createTimer = null) { }
public static System.Action Invoking<T>(this T subject, System.Action<T> action) { }
public static System.Func<TResult> Invoking<T, TResult>(this T subject, System.Func<T, TResult> action) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Action<FluentAssertions.Events.EventMonitorOptions> configureOptions) { }
public static FluentAssertions.Events.IMonitor<T> Monitor<T>(this T eventSource, System.Func<System.DateTime> utcNow = null) { }
public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { }
[System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" +
Expand Down Expand Up @@ -1270,6 +1271,11 @@ namespace FluentAssertions.Events
public string EventName { get; }
public System.Type HandlerType { get; }
}
public class EventMonitorOptions
{
public EventMonitorOptions() { }
public FluentAssertions.Events.EventMonitorOptions IgnoreEventAccessorExceptions() { }
}
public interface IEventRecording : System.Collections.Generic.IEnumerable<FluentAssertions.Events.OccurredEvent>, System.Collections.IEnumerable
{
System.Type EventHandlerType { get; }
Expand Down