diff --git a/Feature.Flags.props b/Feature.Flags.props
index 5abb8a49..36b5ccda 100644
--- a/Feature.Flags.props
+++ b/Feature.Flags.props
@@ -34,6 +34,7 @@
$(DefineConstants);FEATURE_RANDOM_ITEMS
$(DefineConstants);FEATURE_COMPRESSION_STREAM
$(DefineConstants);FEATURE_STOPWATCH_GETELAPSEDTIME
+ $(DefineConstants);FEATURE_PERIODIC_TIMER
$(DefineConstants);FEATURE_PATH_SPAN
$(DefineConstants);FEATURE_FILE_SPAN
$(DefineConstants);FEATURE_GUID_V7
diff --git a/Pipeline/Build.cs b/Pipeline/Build.cs
index 65bdc27a..f5452a74 100644
--- a/Pipeline/Build.cs
+++ b/Pipeline/Build.cs
@@ -20,7 +20,7 @@ partial class Build : NukeBuild
///
/// Afterward, you can update the package reference in `Directory.Packages.props` and reset this flag.
///
- readonly BuildScope BuildScope = BuildScope.Default;
+ readonly BuildScope BuildScope = BuildScope.CoreOnly;
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
diff --git a/Source/Testably.Abstractions.Interface/ITimeSystem.cs b/Source/Testably.Abstractions.Interface/ITimeSystem.cs
index e2de801e..9b3ed5fc 100644
--- a/Source/Testably.Abstractions.Interface/ITimeSystem.cs
+++ b/Source/Testably.Abstractions.Interface/ITimeSystem.cs
@@ -12,6 +12,13 @@ public interface ITimeSystem
///
IDateTime DateTime { get; }
+#if FEATURE_PERIODIC_TIMER
+ ///
+ /// Abstractions for .
+ ///
+ IPeriodicTimerFactory PeriodicTimer { get; }
+#endif
+
///
/// Abstractions for .
///
diff --git a/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimer.cs b/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimer.cs
new file mode 100644
index 00000000..2f260798
--- /dev/null
+++ b/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimer.cs
@@ -0,0 +1,19 @@
+#if FEATURE_PERIODIC_TIMER
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Testably.Abstractions.TimeSystem;
+
+///
+/// Abstractions for .
+///
+public interface IPeriodicTimer : ITimeSystemEntity, IDisposable
+{
+ ///
+ TimeSpan Period { get; set; }
+
+ ///
+ ValueTask WaitForNextTickAsync(CancellationToken cancellationToken = default);
+}
+#endif
diff --git a/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimerFactory.cs b/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimerFactory.cs
new file mode 100644
index 00000000..2c5c03b8
--- /dev/null
+++ b/Source/Testably.Abstractions.Interface/TimeSystem/IPeriodicTimerFactory.cs
@@ -0,0 +1,20 @@
+#if FEATURE_PERIODIC_TIMER
+using System;
+using System.Threading;
+
+namespace Testably.Abstractions.TimeSystem;
+
+///
+/// Factory for abstracting creation of .
+///
+public interface IPeriodicTimerFactory : ITimeSystemEntity
+{
+ ///
+ IPeriodicTimer New(TimeSpan period);
+
+ ///
+ /// Wraps the to the testable .
+ ///
+ IPeriodicTimer Wrap(PeriodicTimer timer);
+}
+#endif
diff --git a/Source/Testably.Abstractions/RealTimeSystem.cs b/Source/Testably.Abstractions/RealTimeSystem.cs
index 348e3291..1be790d5 100644
--- a/Source/Testably.Abstractions/RealTimeSystem.cs
+++ b/Source/Testably.Abstractions/RealTimeSystem.cs
@@ -16,6 +16,9 @@ public sealed class RealTimeSystem : ITimeSystem
public RealTimeSystem()
{
DateTime = new DateTimeWrapper(this);
+#if FEATURE_PERIODIC_TIMER
+ PeriodicTimer = new PeriodicTimerFactory(this);
+#endif
Stopwatch = new StopwatchFactory(this);
Task = new TaskWrapper(this);
Thread = new ThreadWrapper(this);
@@ -27,6 +30,11 @@ public RealTimeSystem()
///
public IDateTime DateTime { get; }
+#if FEATURE_PERIODIC_TIMER
+ ///
+ public IPeriodicTimerFactory PeriodicTimer { get; }
+#endif
+
///
public IStopwatchFactory Stopwatch { get; }
diff --git a/Source/Testably.Abstractions/TimeSystem/PeriodicTimerFactory.cs b/Source/Testably.Abstractions/TimeSystem/PeriodicTimerFactory.cs
new file mode 100644
index 00000000..cb68a707
--- /dev/null
+++ b/Source/Testably.Abstractions/TimeSystem/PeriodicTimerFactory.cs
@@ -0,0 +1,29 @@
+#if FEATURE_PERIODIC_TIMER
+using System;
+using System.Threading;
+
+namespace Testably.Abstractions.TimeSystem;
+
+internal sealed class PeriodicTimerFactory : IPeriodicTimerFactory
+{
+ internal PeriodicTimerFactory(RealTimeSystem timeSystem)
+ {
+ TimeSystem = timeSystem;
+ }
+
+ #region IPeriodicTimerFactory Members
+
+ ///
+ public ITimeSystem TimeSystem { get; }
+
+ ///
+ public IPeriodicTimer New(TimeSpan period)
+ => Wrap(new PeriodicTimer(period));
+
+ ///
+ public IPeriodicTimer Wrap(PeriodicTimer timer)
+ => new PeriodicTimerWrapper(TimeSystem, timer);
+
+ #endregion
+}
+#endif
diff --git a/Source/Testably.Abstractions/TimeSystem/PeriodicTimerWrapper.cs b/Source/Testably.Abstractions/TimeSystem/PeriodicTimerWrapper.cs
new file mode 100644
index 00000000..c7a718e9
--- /dev/null
+++ b/Source/Testably.Abstractions/TimeSystem/PeriodicTimerWrapper.cs
@@ -0,0 +1,40 @@
+#if FEATURE_PERIODIC_TIMER
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Testably.Abstractions.TimeSystem;
+
+internal sealed class PeriodicTimerWrapper : IPeriodicTimer
+{
+ private readonly PeriodicTimer _periodicTimer;
+
+ internal PeriodicTimerWrapper(ITimeSystem timeSystem, PeriodicTimer periodicTimer)
+ {
+ TimeSystem = timeSystem;
+ _periodicTimer = periodicTimer;
+ }
+
+ #region IPeriodicTimer Members
+
+ ///
+ public TimeSpan Period
+ {
+ get => _periodicTimer.Period;
+ set => _periodicTimer.Period = value;
+ }
+
+ ///
+ public ITimeSystem TimeSystem { get; }
+
+ ///
+ public void Dispose()
+ => _periodicTimer.Dispose();
+
+ ///
+ public ValueTask WaitForNextTickAsync(CancellationToken cancellationToken = default)
+ => _periodicTimer.WaitForNextTickAsync(cancellationToken);
+
+ #endregion
+}
+#endif
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net10.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net10.0.txt
index 61b9410e..62263d28 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net10.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net10.0.txt
@@ -27,6 +27,7 @@ namespace Testably.Abstractions
{
public RealTimeSystem() { }
public Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ public Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
public Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
public Testably.Abstractions.TimeSystem.ITask Task { get; }
public Testably.Abstractions.TimeSystem.IThread Thread { get; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net8.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net8.0.txt
index 525e9347..3773771e 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net8.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net8.0.txt
@@ -27,6 +27,7 @@ namespace Testably.Abstractions
{
public RealTimeSystem() { }
public Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ public Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
public Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
public Testably.Abstractions.TimeSystem.ITask Task { get; }
public Testably.Abstractions.TimeSystem.IThread Thread { get; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt
index 3b58557c..8bd2fd1b 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt
@@ -27,6 +27,7 @@ namespace Testably.Abstractions
{
public RealTimeSystem() { }
public Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ public Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
public Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
public Testably.Abstractions.TimeSystem.ITask Task { get; }
public Testably.Abstractions.TimeSystem.IThread Thread { get; }
diff --git a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net10.0.txt b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net10.0.txt
index be88a6a3..97f9f720 100644
--- a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net10.0.txt
+++ b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net10.0.txt
@@ -67,6 +67,7 @@ namespace Testably.Abstractions
public interface ITimeSystem
{
Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
Testably.Abstractions.TimeSystem.ITask Task { get; }
Testably.Abstractions.TimeSystem.IThread Thread { get; }
@@ -141,6 +142,16 @@ namespace Testably.Abstractions.TimeSystem
System.DateTime UnixEpoch { get; }
System.DateTime UtcNow { get; }
}
+ public interface IPeriodicTimer : System.IDisposable, Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ System.TimeSpan Period { get; set; }
+ System.Threading.Tasks.ValueTask WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default);
+ }
+ public interface IPeriodicTimerFactory : Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ Testably.Abstractions.TimeSystem.IPeriodicTimer New(System.TimeSpan period);
+ Testably.Abstractions.TimeSystem.IPeriodicTimer Wrap(System.Threading.PeriodicTimer timer);
+ }
public interface IStopwatch : Testably.Abstractions.TimeSystem.ITimeSystemEntity
{
System.TimeSpan Elapsed { get; }
diff --git a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net8.0.txt b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net8.0.txt
index 25decbac..d4bec9fe 100644
--- a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net8.0.txt
+++ b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net8.0.txt
@@ -58,6 +58,7 @@ namespace Testably.Abstractions
public interface ITimeSystem
{
Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
Testably.Abstractions.TimeSystem.ITask Task { get; }
Testably.Abstractions.TimeSystem.IThread Thread { get; }
@@ -123,6 +124,16 @@ namespace Testably.Abstractions.TimeSystem
System.DateTime UnixEpoch { get; }
System.DateTime UtcNow { get; }
}
+ public interface IPeriodicTimer : System.IDisposable, Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ System.TimeSpan Period { get; set; }
+ System.Threading.Tasks.ValueTask WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default);
+ }
+ public interface IPeriodicTimerFactory : Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ Testably.Abstractions.TimeSystem.IPeriodicTimer New(System.TimeSpan period);
+ Testably.Abstractions.TimeSystem.IPeriodicTimer Wrap(System.Threading.PeriodicTimer timer);
+ }
public interface IStopwatch : Testably.Abstractions.TimeSystem.ITimeSystemEntity
{
System.TimeSpan Elapsed { get; }
diff --git a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt
index f9a857b7..ed0cf769 100644
--- a/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt
+++ b/Tests/Api/Testably.Abstractions.Core.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt
@@ -60,6 +60,7 @@ namespace Testably.Abstractions
public interface ITimeSystem
{
Testably.Abstractions.TimeSystem.IDateTime DateTime { get; }
+ Testably.Abstractions.TimeSystem.IPeriodicTimerFactory PeriodicTimer { get; }
Testably.Abstractions.TimeSystem.IStopwatchFactory Stopwatch { get; }
Testably.Abstractions.TimeSystem.ITask Task { get; }
Testably.Abstractions.TimeSystem.IThread Thread { get; }
@@ -127,6 +128,16 @@ namespace Testably.Abstractions.TimeSystem
System.DateTime UnixEpoch { get; }
System.DateTime UtcNow { get; }
}
+ public interface IPeriodicTimer : System.IDisposable, Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ System.TimeSpan Period { get; set; }
+ System.Threading.Tasks.ValueTask WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default);
+ }
+ public interface IPeriodicTimerFactory : Testably.Abstractions.TimeSystem.ITimeSystemEntity
+ {
+ Testably.Abstractions.TimeSystem.IPeriodicTimer New(System.TimeSpan period);
+ Testably.Abstractions.TimeSystem.IPeriodicTimer Wrap(System.Threading.PeriodicTimer timer);
+ }
public interface IStopwatch : Testably.Abstractions.TimeSystem.ITimeSystemEntity
{
System.TimeSpan Elapsed { get; }
diff --git a/Tests/Testably.Abstractions.Parity.Tests/ParityTests.cs b/Tests/Testably.Abstractions.Parity.Tests/ParityTests.cs
index c486e168..4ebe8b3d 100644
--- a/Tests/Testably.Abstractions.Parity.Tests/ParityTests.cs
+++ b/Tests/Testably.Abstractions.Parity.Tests/ParityTests.cs
@@ -13,12 +13,8 @@ namespace Testably.Abstractions.Parity.Tests;
public abstract class ParityTests(
TestHelpers.Parity parity)
{
- #region Test Setup
-
public TestHelpers.Parity Parity { get; } = parity;
- #endregion
-
[Test]
public async Task IDirectory_EnsureParityWith_Directory()
{
@@ -121,23 +117,25 @@ public async Task IPath_EnsureParityWith_Path()
await That(parityErrors).IsEmpty();
}
+#if FEATURE_PERIODIC_TIMER
[Test]
- public async Task IRandomAndIRandomFactory_EnsureParityWith_Random()
+ public async Task
+ IPeriodicTimerAndIPeriodicTimerFactory_EnsureParityWith_PeriodicTimer()
{
- List parityErrors = Parity.Random
- .GetErrorsToInstanceType(
- typeof(Random));
+ List parityErrors = Parity.PeriodicTimer
+ .GetErrorsToInstanceType(
+ typeof(PeriodicTimer));
await That(parityErrors).IsEmpty();
}
+#endif
[Test]
- public async Task
- ITimerAndITimerFactory_EnsureParityWith_Timer()
+ public async Task IRandomAndIRandomFactory_EnsureParityWith_Random()
{
- List parityErrors = Parity.Timer
- .GetErrorsToInstanceType(
- typeof(Timer));
+ List parityErrors = Parity.Random
+ .GetErrorsToInstanceType(
+ typeof(Random));
await That(parityErrors).IsEmpty();
}
@@ -156,6 +154,17 @@ public async Task
await That(parityErrors).IsEmpty();
}
+ [Test]
+ public async Task
+ ITimerAndITimerFactory_EnsureParityWith_Timer()
+ {
+ List parityErrors = Parity.Timer
+ .GetErrorsToInstanceType(
+ typeof(Timer));
+
+ await That(parityErrors).IsEmpty();
+ }
+
[Test]
public async Task IZipArchive_EnsureParityWith_ZipArchive()
{
diff --git a/Tests/Testably.Abstractions.Parity.Tests/TestHelpers/Parity.cs b/Tests/Testably.Abstractions.Parity.Tests/TestHelpers/Parity.cs
index f6b6edeb..0a120b49 100644
--- a/Tests/Testably.Abstractions.Parity.Tests/TestHelpers/Parity.cs
+++ b/Tests/Testably.Abstractions.Parity.Tests/TestHelpers/Parity.cs
@@ -73,6 +73,16 @@ public class Parity
#pragma warning restore CS0618
});
+#if FEATURE_PERIODIC_TIMER
+ public ParityCheck PeriodicTimer { get; } = new(excludeConstructors:
+ [
+ typeof(PeriodicTimer).GetConstructor([
+ typeof(TimeSpan),
+ typeof(TimeProvider),
+ ]),
+ ]);
+#endif
+
public ParityCheck Random { get; } = new();
public ParityCheck Stopwatch { get; } = new(excludeMethods: