Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Testably.Abstractions.Testing/MockTimeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public MockTimeSystem(ITimeProviderFactory timeProvider, Func<MockTimeSystemOpti
_threadMock = new ThreadMock(this, _callbackHandler, initialization.AutoAdvance);
_taskMock = new TaskMock(this, _callbackHandler, initialization.AutoAdvance);
#if FEATURE_PERIODIC_TIMER
_periodicTimerFactoryMock = new PeriodicTimerFactoryMock(this, initialization.AutoAdvance);
_periodicTimerFactoryMock = new PeriodicTimerFactoryMock(this, _callbackHandler, initialization.AutoAdvance);
#endif
_timerFactoryMock = new TimerFactoryMock(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
namespace Testably.Abstractions.Testing.TimeSystem;

/// <summary>
/// The callback handler for the <see cref="MockTimeSystem" />
/// The callback handler for the <see cref="MockTimeSystem" />.
/// </summary>
public interface INotificationHandler
{
#if FEATURE_PERIODIC_TIMER
/// <summary>
/// Notifications for the <see cref="IPeriodicTimer" />.
/// </summary>
IPeriodicTimerNotificationHandler PeriodicTimer { get; }
#endif

/// <summary>
/// Callback executed when any of the following <c>DateTime</c> read methods is called:<br />
/// - <see cref="IDateTime.Now" /><br />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#if FEATURE_PERIODIC_TIMER
using System;
using Testably.Abstractions.TimeSystem;

namespace Testably.Abstractions.Testing.TimeSystem;

/// <summary>
/// The callback handler for the <see cref="IPeriodicTimer" /> of the <see cref="MockTimeSystem" />.
/// </summary>
public interface IPeriodicTimerNotificationHandler
{
/// <summary>
/// Callback executed when any periodic timer is waiting for the next tick.
/// </summary>
/// <param name="callback">
/// (optional) The callback to execute when the periodic timer is waiting for the next tick. The parameter is the periodic timer which is waiting for the next tick.
/// </param>
/// <param name="predicate">
/// (optional) A predicate used to filter which callbacks should be notified.<br />
/// If set to <see langword="null" /> (default value) all callbacks are notified.
/// </param>
/// <returns>A <see cref="IAwaitableCallback{IPeriodicTimer}" /> to un-register the callback on dispose.</returns>
IAwaitableCallback<IPeriodicTimer> WaitingForNextTick(
Action<IPeriodicTimer>? callback = null,
Func<IPeriodicTimer, bool>? predicate = null);
}
#endif
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
using System;
#if FEATURE_PERIODIC_TIMER
using Testably.Abstractions.TimeSystem;
#endif

namespace Testably.Abstractions.Testing.TimeSystem;

internal sealed class NotificationHandler(MockTimeSystem mockTimeSystem) : INotificationHandler
internal sealed class NotificationHandler(MockTimeSystem mockTimeSystem)
#if FEATURE_PERIODIC_TIMER
: INotificationHandler, IPeriodicTimerNotificationHandler
#else
: INotificationHandler
#endif
{
private readonly Notification.INotificationFactory<DateTime>
_dateTimeReadCallbacks = Notification.CreateFactory<DateTime>();

#if FEATURE_PERIODIC_TIMER
private readonly Notification.INotificationFactory<IPeriodicTimer>
_periodicTimerWaitingForNextTickCallbacks = Notification.CreateFactory<IPeriodicTimer>();
#endif

private readonly Notification.INotificationFactory<TimeSpan>
_taskDelayCallbacks = Notification.CreateFactory<TimeSpan>();

Expand All @@ -18,6 +31,11 @@ private readonly Notification.INotificationFactory<DateTime>

#region INotificationHandler Members

#if FEATURE_PERIODIC_TIMER
/// <inheritdoc cref="INotificationHandler.PeriodicTimer" />
public IPeriodicTimerNotificationHandler PeriodicTimer => this;
#endif

/// <inheritdoc cref="INotificationHandler.DateTimeRead(Action{DateTime}?, Func{DateTime, bool}?)" />
public IAwaitableCallback<DateTime> DateTimeRead(
Action<DateTime>? callback = null,
Expand Down Expand Up @@ -45,9 +63,27 @@ public IAwaitableCallback<DateTime> TimeChanged(

#endregion

#if FEATURE_PERIODIC_TIMER

#region IPeriodicTimerNotificationHandler Members

/// <inheritdoc cref="IPeriodicTimerNotificationHandler.WaitingForNextTick(Action{IPeriodicTimer}?, Func{IPeriodicTimer, bool}?)" />
public IAwaitableCallback<IPeriodicTimer> WaitingForNextTick(
Action<IPeriodicTimer>? callback = null, Func<IPeriodicTimer, bool>? predicate = null)
=> _periodicTimerWaitingForNextTickCallbacks.RegisterCallback(callback, predicate);

#endregion

#endif

public void InvokeDateTimeReadCallbacks(DateTime now)
=> _dateTimeReadCallbacks.InvokeCallbacks(now);

#if FEATURE_PERIODIC_TIMER
public void InvokePeriodicTimerWaitingForNextTick(IPeriodicTimer timer)
=> _periodicTimerWaitingForNextTickCallbacks.InvokeCallbacks(timer);
#endif

public void InvokeTaskDelayCallbacks(TimeSpan delay)
=> _taskDelayCallbacks.InvokeCallbacks(delay);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ namespace Testably.Abstractions.Testing.TimeSystem;
internal sealed class PeriodicTimerFactoryMock : IPeriodicTimerFactory
{
private readonly MockTimeSystem _mockTimeSystem;
private readonly NotificationHandler _callbackHandler;
private readonly bool _autoAdvance;

internal PeriodicTimerFactoryMock(MockTimeSystem timeSystem, bool autoAdvance)
internal PeriodicTimerFactoryMock(MockTimeSystem timeSystem,
NotificationHandler callbackHandler, bool autoAdvance)
{
_mockTimeSystem = timeSystem;
_callbackHandler = callbackHandler;
_autoAdvance = autoAdvance;
}

Expand All @@ -24,7 +27,7 @@ internal PeriodicTimerFactoryMock(MockTimeSystem timeSystem, bool autoAdvance)

/// <inheritdoc cref="IPeriodicTimerFactory.New(TimeSpan)" />
public IPeriodicTimer New(TimeSpan period)
=> new PeriodicTimerMock(_mockTimeSystem, period, _autoAdvance);
=> new PeriodicTimerMock(_mockTimeSystem, _callbackHandler, period, _autoAdvance);

/// <inheritdoc cref="IPeriodicTimerFactory.Wrap(PeriodicTimer)" />
public IPeriodicTimer Wrap(PeriodicTimer timer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ internal sealed class PeriodicTimerMock : IPeriodicTimer
private bool _isDisposed;
private long _lastTime;
private readonly MockTimeSystem _timeSystem;
private readonly NotificationHandler _callbackHandler;

internal PeriodicTimerMock(MockTimeSystem timeSystem,
TimeSpan period, bool autoAdvance)
internal PeriodicTimerMock(
MockTimeSystem timeSystem,
NotificationHandler callbackHandler,
TimeSpan period,
bool autoAdvance)
{
ThrowIfPeriodIsInvalid(period, nameof(period));

_timeSystem = timeSystem;
_callbackHandler = callbackHandler;
_autoAdvance = autoAdvance;
_lastTime = _timeSystem.TimeProvider.ElapsedTicks;
Period = period;
Expand Down Expand Up @@ -58,6 +63,7 @@ public void Dispose()
return false;
}

_callbackHandler.InvokePeriodicTimerWaitingForNextTick(this);
long now = _timeSystem.TimeProvider.ElapsedTicks;
long nextTime = _lastTime + Period.Ticks;
if (nextTime > now)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,16 @@ namespace Testably.Abstractions.Testing.TimeSystem
{
public interface INotificationHandler
{
Testably.Abstractions.Testing.TimeSystem.IPeriodicTimerNotificationHandler PeriodicTimer { get; }
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> DateTimeRead(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> TaskDelay(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> ThreadSleep(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> TimeChanged(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, System.DateTime, bool>? predicate = null);
}
public interface IPeriodicTimerNotificationHandler
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.TimeSystem.IPeriodicTimer> WaitingForNextTick(System.Action<Testably.Abstractions.TimeSystem.IPeriodicTimer>? callback = null, System.Func<Testably.Abstractions.TimeSystem.IPeriodicTimer, bool>? predicate = null);
}
public interface ITimeProvider
{
long ElapsedTicks { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,16 @@ namespace Testably.Abstractions.Testing.TimeSystem
{
public interface INotificationHandler
{
Testably.Abstractions.Testing.TimeSystem.IPeriodicTimerNotificationHandler PeriodicTimer { get; }
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> DateTimeRead(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> TaskDelay(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> ThreadSleep(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> TimeChanged(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, System.DateTime, bool>? predicate = null);
}
public interface IPeriodicTimerNotificationHandler
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.TimeSystem.IPeriodicTimer> WaitingForNextTick(System.Action<Testably.Abstractions.TimeSystem.IPeriodicTimer>? callback = null, System.Func<Testably.Abstractions.TimeSystem.IPeriodicTimer, bool>? predicate = null);
}
public interface ITimeProvider
{
long ElapsedTicks { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,16 @@ namespace Testably.Abstractions.Testing.TimeSystem
{
public interface INotificationHandler
{
Testably.Abstractions.Testing.TimeSystem.IPeriodicTimerNotificationHandler PeriodicTimer { get; }
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> DateTimeRead(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> TaskDelay(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.TimeSpan> ThreadSleep(System.Action<System.TimeSpan>? callback = null, System.Func<System.TimeSpan, bool>? predicate = null);
Testably.Abstractions.Testing.IAwaitableCallback<System.DateTime> TimeChanged(System.Action<System.DateTime>? callback = null, System.Func<System.DateTime, System.DateTime, bool>? predicate = null);
}
public interface IPeriodicTimerNotificationHandler
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.TimeSystem.IPeriodicTimer> WaitingForNextTick(System.Action<Testably.Abstractions.TimeSystem.IPeriodicTimer>? callback = null, System.Func<Testably.Abstractions.TimeSystem.IPeriodicTimer, bool>? predicate = null);
}
public interface ITimeProvider
{
long ElapsedTicks { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading;
using Testably.Abstractions.Testing.Tests.TestHelpers;
using Testably.Abstractions.TimeSystem;

namespace Testably.Abstractions.Testing.Tests.TimeSystem;

Expand Down Expand Up @@ -87,6 +88,129 @@ public async Task OnDateTimeRead_UtcNow_ShouldExecuteCallbackWithCorrectParamete
await That(receivedTime).IsEqualTo(expectedTime);
}

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task OnPeriodicTimerWaitingForNextTick_DisposedCallback_ShouldNotBeCalled()
{
MockTimeSystem timeSystem = new();
IPeriodicTimer? receivedTimer = null;
using IPeriodicTimer periodicTimer =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));
IDisposable disposable =
timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer = t);

disposable.Dispose();
await periodicTimer.WaitForNextTickAsync();

await That(receivedTimer).IsNull();
}
#endif

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task
OnPeriodicTimerWaitingForNextTick_MultipleCallbacks_DisposeOne_ShouldCallOtherCallbacks()
{
MockTimeSystem timeSystem = new();
IPeriodicTimer? receivedTimer1 = null;
IPeriodicTimer? receivedTimer2 = null;
using IPeriodicTimer periodicTimer =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));

using (timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer1 = t))
{
timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer2 = t).Dispose();
await periodicTimer.WaitForNextTickAsync();
}

await That(receivedTimer1).IsEqualTo(periodicTimer);
await That(receivedTimer2).IsNull();
}
#endif

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task OnPeriodicTimerWaitingForNextTick_MultipleCallbacks_ShouldAllBeCalled()
{
MockTimeSystem timeSystem = new();
IPeriodicTimer? receivedTimer1 = null;
IPeriodicTimer? receivedTimer2 = null;
using IPeriodicTimer periodicTimer =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));

using (timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer1 = t))
{
using (timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer2 = t))
{
await periodicTimer.WaitForNextTickAsync();
}
}

await That(receivedTimer1).IsEqualTo(periodicTimer);
await That(receivedTimer2).IsEqualTo(periodicTimer);
}
#endif

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task
OnPeriodicTimerWaitingForNextTick_ShouldBeCalledOnEachWaitForNextTickAsync()
{
MockTimeSystem timeSystem = new();
int callbackCount = 0;
using IPeriodicTimer periodicTimer =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));

using (timeSystem.On.PeriodicTimer.WaitingForNextTick(_ => callbackCount++))
{
await periodicTimer.WaitForNextTickAsync();
await periodicTimer.WaitForNextTickAsync();
await periodicTimer.WaitForNextTickAsync();
}

await That(callbackCount).IsEqualTo(3);
}
#endif

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task OnPeriodicTimerWaitingForNextTick_ShouldExecuteCallbackWithCorrectParameter()
{
MockTimeSystem timeSystem = new();
IPeriodicTimer? receivedTimer = null;
using IPeriodicTimer periodicTimer =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));

using (timeSystem.On.PeriodicTimer.WaitingForNextTick(t => receivedTimer = t))
{
await periodicTimer.WaitForNextTickAsync();
}

await That(receivedTimer).IsEqualTo(periodicTimer);
}
#endif

#if FEATURE_PERIODIC_TIMER
[Test]
public async Task OnPeriodicTimerWaitingForNextTick_WithPredicate_ShouldFilterCallbacks()
{
MockTimeSystem timeSystem = new();
int callbackCount = 0;
using IPeriodicTimer periodicTimer1 =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(1));
using IPeriodicTimer periodicTimer2 =
timeSystem.PeriodicTimer.New(TimeSpan.FromSeconds(2));

using IAwaitableCallback<IPeriodicTimer> _ = timeSystem.On.PeriodicTimer.WaitingForNextTick(
callback: _ => callbackCount++,
predicate: p => p == periodicTimer1);
await periodicTimer1.WaitForNextTickAsync();
await That(callbackCount).IsEqualTo(1);
await periodicTimer2.WaitForNextTickAsync();
await That(callbackCount).IsEqualTo(1);
}
#endif

[Test]
public async Task OnTaskDelay_DisposedCallback_ShouldNotBeCalled()
{
Expand Down
Loading
Loading