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 16 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
36 changes: 30 additions & 6 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -360,7 +360,8 @@ public static StringCollectionAssertions Should(this IEnumerable<string> @this)
/// current <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
[Pure]
public static GenericDictionaryAssertions<IDictionary<TKey, TValue>, TKey, TValue> Should<TKey, TValue>(this IDictionary<TKey, TValue> actualValue)
public static GenericDictionaryAssertions<IDictionary<TKey, TValue>, TKey, TValue> Should<TKey, TValue>(
this IDictionary<TKey, TValue> actualValue)
{
return new GenericDictionaryAssertions<IDictionary<TKey, TValue>, TKey, TValue>(actualValue);
}
Expand All @@ -370,7 +371,8 @@ public static StringCollectionAssertions Should(this IEnumerable<string> @this)
/// current <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{TKey, TValue}"/>.
/// </summary>
[Pure]
public static GenericDictionaryAssertions<IEnumerable<KeyValuePair<TKey, TValue>>, TKey, TValue> Should<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> actualValue)
public static GenericDictionaryAssertions<IEnumerable<KeyValuePair<TKey, TValue>>, TKey, TValue> Should<TKey, TValue>(
this IEnumerable<KeyValuePair<TKey, TValue>> actualValue)
{
return new GenericDictionaryAssertions<IEnumerable<KeyValuePair<TKey, TValue>>, TKey, TValue>(actualValue);
}
Expand All @@ -380,7 +382,8 @@ public static StringCollectionAssertions Should(this IEnumerable<string> @this)
/// current <typeparamref name="TCollection"/>.
/// </summary>
[Pure]
public static GenericDictionaryAssertions<TCollection, TKey, TValue> Should<TCollection, TKey, TValue>(this TCollection actualValue)
public static GenericDictionaryAssertions<TCollection, TKey, TValue> Should<TCollection, TKey, TValue>(
this TCollection actualValue)
where TCollection : IEnumerable<KeyValuePair<TKey, TValue>>
{
return new GenericDictionaryAssertions<TCollection, TKey, TValue>(actualValue);
Expand Down Expand Up @@ -916,7 +919,27 @@ public static TaskCompletionSourceAssertions<T> Should<T>(this TaskCompletionSou
/// <exception cref="ArgumentNullException">Thrown if <paramref name="eventSource"/> is Null.</exception>
public static IMonitor<T> Monitor<T>(this T eventSource, Func<DateTime> utcNow = null)
{
return new EventMonitor<T>(eventSource, utcNow ?? (() => DateTime.UtcNow));
return Monitor(eventSource, o =>
{
o.ConfigureTimestampProvider(utcNow);
});
}

/// <summary>
/// Starts monitoring <paramref name="eventSource"/> for its events using the given <paramref name="configureOptions"/>.
/// </summary>
/// <param name="eventSource">The object for which to monitor the events.</param>
/// <param name="configureOptions">
/// Options to configure the EventMonitor.
/// </param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="eventSource"/> is Null.</exception>
public static IMonitor<T> Monitor<T>(this T eventSource, Action<EventMonitorOptions> configureOptions)
{
Guard.ThrowIfArgumentIsNull(configureOptions, nameof(configureOptions));

var options = new EventMonitorOptions();
configureOptions(options);
return new EventMonitor<T>(eventSource, options);
}

#endif
Expand Down Expand Up @@ -968,7 +991,7 @@ public static void Should<TAssertions>(this DateTimeOffsetAssertions<TAssertions
InvalidShouldCall();
}

#if NET6_0_OR_GREATER
#if NET6_0_OR_GREATER

/// <inheritdoc cref="Should(ExecutionTimeAssertions)" />
[Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)]
Expand Down Expand Up @@ -1078,7 +1101,8 @@ public static void Should<TAssertions>(this DateTimeOffsetRangeAssertions<TAsser
[DoesNotReturn]
private static void InvalidShouldCall()
{
throw new InvalidOperationException("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
throw new InvalidOperationException(
"You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
}

#endregion
Expand Down
40 changes: 36 additions & 4 deletions Src/FluentAssertions/Events/EventMonitor.cs
Expand Up @@ -19,18 +19,22 @@ internal class EventMonitor<T> : IMonitor<T>
private readonly ConcurrentDictionary<string, EventRecorder> recorderMap =
new ConcurrentDictionary<string, EventRecorder>();

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(eventSource, nameof(eventSource), "Event monitor needs configuration.");
apazureck marked this conversation as resolved.
Show resolved Hide resolved

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,20 +121,48 @@ 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 _))
{
var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow, threadSafeSequenceGenerator);
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 (!recorderMap.TryRemove(eventInfo.Name, out _))
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
{
throw new InvalidOperationException(
$"Could not remove event {eventInfo.Name} with broken event accessor from event recording.");
}
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
45 changes: 45 additions & 0 deletions Src/FluentAssertions/Events/EventMonitorOptions.cs
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;

namespace FluentAssertions.Events;

/// <summary>
/// Settings for the <see cref="EventMonitor{T}"/>.
/// </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>
/// 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>
/// <returns>The options instance for method stacking.</returns>
public EventMonitorOptions IgnoreEventAccessorExceptions()
{
ShouldIgnoreEventAccessorExceptions = true;
return this;
}

/// <summary>
/// Sets the timestamp provider. default it is <see cref="DateTime.Now"/>.
apazureck marked this conversation as resolved.
Show resolved Hide resolved
apazureck marked this conversation as resolved.
Show resolved Hide resolved
/// </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;
}
}
Expand Up @@ -38,6 +38,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 @@ -1255,6 +1256,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
Expand Up @@ -38,6 +38,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 @@ -1267,6 +1268,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
Expand Up @@ -38,6 +38,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 @@ -1255,6 +1256,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
Expand Up @@ -38,6 +38,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 @@ -1255,6 +1256,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
Expand Up @@ -38,6 +38,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 @@ -1255,6 +1256,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