Browse Source

Replace ISystemClock with TimeProvider in OutputCaching, ResponseCaching (#47753)

Chris Ross 2 years ago
parent
commit
b362c45005

+ 0 - 15
src/Middleware/OutputCaching/src/ISystemClock.cs

@@ -1,15 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.OutputCaching;
-
-/// <summary>
-/// Abstracts the system clock to facilitate testing.
-/// </summary>
-internal interface ISystemClock
-{
-    /// <summary>
-    /// Retrieves the current system time in UTC.
-    /// </summary>
-    DateTimeOffset UtcNow { get; }
-}

+ 2 - 2
src/Middleware/OutputCaching/src/OutputCacheMiddleware.cs

@@ -243,7 +243,7 @@ internal sealed class OutputCacheMiddleware
         }
 
         context.CachedResponse = cacheEntry;
-        context.ResponseTime = _options.SystemClock.UtcNow;
+        context.ResponseTime = _options.TimeProvider.GetUtcNow();
         var cacheEntryAge = context.ResponseTime.Value - context.CachedResponse.Created;
         context.CachedEntryAge = cacheEntryAge > TimeSpan.Zero ? cacheEntryAge : TimeSpan.Zero;
 
@@ -464,7 +464,7 @@ internal sealed class OutputCacheMiddleware
         if (!context.ResponseStarted)
         {
             context.ResponseStarted = true;
-            context.ResponseTime = _options.SystemClock.UtcNow;
+            context.ResponseTime = _options.TimeProvider.GetUtcNow();
 
             return true;
         }

+ 1 - 4
src/Middleware/OutputCaching/src/OutputCacheOptions.cs

@@ -1,8 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.ComponentModel;
-
 namespace Microsoft.AspNetCore.OutputCaching;
 
 /// <summary>
@@ -45,8 +43,7 @@ public class OutputCacheOptions
     /// <summary>
     /// For testing purposes only.
     /// </summary>
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    internal ISystemClock SystemClock { get; set; } = new SystemClock();
+    internal TimeProvider TimeProvider { get; set; } = TimeProvider.System;
 
     /// <summary>
     /// Defines a <see cref="IOutputCachePolicy"/> which can be referenced by name.

+ 0 - 15
src/Middleware/OutputCaching/src/SystemClock.cs

@@ -1,15 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.OutputCaching;
-
-/// <summary>
-/// Provides access to the normal system clock.
-/// </summary>
-internal sealed class SystemClock : ISystemClock
-{
-    /// <summary>
-    /// Retrieves the current system time in UTC.
-    /// </summary>
-    public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
-}

+ 20 - 35
src/Middleware/OutputCaching/test/OutputCacheMiddlewareTests.cs

@@ -334,44 +334,38 @@ public class OutputCacheMiddlewareTests
     [Fact]
     public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var middleware = TestUtils.CreateTestMiddleware(options: new OutputCacheOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
         context.ResponseTime = null;
 
         middleware.StartResponse(context);
 
-        Assert.Equal(clock.UtcNow, context.ResponseTime);
+        Assert.Equal(timeProvider.GetUtcNow(), context.ResponseTime);
     }
 
     [Fact]
     public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var middleware = TestUtils.CreateTestMiddleware(options: new OutputCacheOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
-        var initialTime = clock.UtcNow;
+        var initialTime = timeProvider.GetUtcNow();
         context.ResponseTime = null;
 
         middleware.StartResponse(context);
         Assert.Equal(initialTime, context.ResponseTime);
 
-        clock.UtcNow += TimeSpan.FromSeconds(10);
+        timeProvider.Advance(TimeSpan.FromSeconds(10));
 
         middleware.StartResponse(context);
-        Assert.NotEqual(clock.UtcNow, context.ResponseTime);
+        Assert.NotEqual(timeProvider.GetUtcNow(), context.ResponseTime);
         Assert.Equal(initialTime, context.ResponseTime);
     }
 
@@ -393,20 +387,17 @@ public class OutputCacheMiddlewareTests
     {
         // The Expires header should not be used when set in the response
 
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.MinValue
-        };
+        var timeProvider = new TestTimeProvider(DateTimeOffset.MinValue);
         var options = new OutputCacheOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         };
         var sink = new TestSink();
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: options);
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.ResponseTime = timeProvider.GetUtcNow();
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 
@@ -419,25 +410,22 @@ public class OutputCacheMiddlewareTests
     {
         // The MaxAge header should not be used if set in the response
 
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var sink = new TestSink();
         var options = new OutputCacheOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         };
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: options);
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
+        context.ResponseTime = timeProvider.GetUtcNow();
         context.HttpContext.Response.Headers.CacheControl = new CacheControlHeaderValue()
         {
             MaxAge = TimeSpan.FromSeconds(12)
         }.ToString();
 
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 
@@ -448,25 +436,22 @@ public class OutputCacheMiddlewareTests
     [Fact]
     public void FinalizeCacheHeadersAsync_ResponseValidity_UseSharedMaxAgeIfAvailable()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var sink = new TestSink();
         var options = new OutputCacheOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         };
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: options);
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
+        context.ResponseTime = timeProvider.GetUtcNow();
         context.HttpContext.Response.Headers.CacheControl = new CacheControlHeaderValue()
         {
             MaxAge = TimeSpan.FromSeconds(12),
             SharedMaxAge = TimeSpan.FromSeconds(13)
         }.ToString();
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 

+ 17 - 3
src/Middleware/OutputCaching/test/TestUtils.cs

@@ -146,7 +146,7 @@ internal class TestUtils
                             {
                                 outputCachingOptions.MaximumBodySize = options.MaximumBodySize;
                                 outputCachingOptions.UseCaseSensitivePaths = options.UseCaseSensitivePaths;
-                                outputCachingOptions.SystemClock = options.SystemClock;
+                                outputCachingOptions.TimeProvider = options.TimeProvider;
                                 outputCachingOptions.BasePolicies = options.BasePolicies;
                                 outputCachingOptions.DefaultExpirationTimeSpan = options.DefaultExpirationTimeSpan;
                                 outputCachingOptions.SizeLimit = options.SizeLimit;
@@ -347,9 +347,23 @@ internal class TestOutputCache : IOutputCacheStore
     }
 }
 
-internal class TestClock : ISystemClock
+internal class TestTimeProvider : TimeProvider
 {
-    public DateTimeOffset UtcNow { get; set; }
+    private DateTimeOffset _current;
+
+    public TestTimeProvider() : this(DateTimeOffset.UtcNow) { }
+
+    public TestTimeProvider(DateTimeOffset current)
+    {
+        _current = current;
+    }
+
+    public override DateTimeOffset GetUtcNow() => _current;
+
+    public void Advance(TimeSpan timeSpan)
+    {
+        _current += timeSpan;
+    }
 }
 
 internal class AllowTestPolicy : IOutputCachePolicy

+ 0 - 15
src/Middleware/ResponseCaching/src/Interfaces/ISystemClock.cs

@@ -1,15 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.ResponseCaching;
-
-/// <summary>
-/// Abstracts the system clock to facilitate testing.
-/// </summary>
-internal interface ISystemClock
-{
-    /// <summary>
-    /// Retrieves the current system time in UTC.
-    /// </summary>
-    DateTimeOffset UtcNow { get; }
-}

+ 2 - 2
src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs

@@ -142,7 +142,7 @@ public class ResponseCachingMiddleware
 
         context.CachedResponse = cachedResponse;
         context.CachedResponseHeaders = cachedResponse.Headers;
-        context.ResponseTime = _options.SystemClock.UtcNow;
+        context.ResponseTime = _options.TimeProvider.GetUtcNow();
         var cachedEntryAge = context.ResponseTime.Value - context.CachedResponse.Created;
         context.CachedEntryAge = cachedEntryAge > TimeSpan.Zero ? cachedEntryAge : TimeSpan.Zero;
 
@@ -374,7 +374,7 @@ public class ResponseCachingMiddleware
         if (!context.ResponseStarted)
         {
             context.ResponseStarted = true;
-            context.ResponseTime = _options.SystemClock.UtcNow;
+            context.ResponseTime = _options.TimeProvider.GetUtcNow();
 
             return true;
         }

+ 1 - 4
src/Middleware/ResponseCaching/src/ResponseCachingOptions.cs

@@ -1,8 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.ComponentModel;
-
 namespace Microsoft.AspNetCore.ResponseCaching;
 
 /// <summary>
@@ -31,6 +29,5 @@ public class ResponseCachingOptions
     /// <summary>
     /// For testing purposes only.
     /// </summary>
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    internal ISystemClock SystemClock { get; set; } = new SystemClock();
+    internal TimeProvider TimeProvider { get; set; } = TimeProvider.System;
 }

+ 0 - 15
src/Middleware/ResponseCaching/src/SystemClock.cs

@@ -1,15 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.ResponseCaching;
-
-/// <summary>
-/// Provides access to the normal system clock.
-/// </summary>
-internal sealed class SystemClock : ISystemClock
-{
-    /// <summary>
-    /// Retrieves the current system time in UTC.
-    /// </summary>
-    public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
-}

+ 21 - 36
src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs

@@ -354,46 +354,40 @@ public class ResponseCachingMiddlewareTests
     }
 
     [Fact]
-    public void StartResponsegAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
+    public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
         context.ResponseTime = null;
 
         middleware.StartResponse(context);
 
-        Assert.Equal(clock.UtcNow, context.ResponseTime);
+        Assert.Equal(timeProvider.GetUtcNow(), context.ResponseTime);
     }
 
     [Fact]
     public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
-        var initialTime = clock.UtcNow;
+        var initialTime = timeProvider.GetUtcNow();
         context.ResponseTime = null;
 
         middleware.StartResponse(context);
         Assert.Equal(initialTime, context.ResponseTime);
 
-        clock.UtcNow += TimeSpan.FromSeconds(10);
+        timeProvider.Advance(TimeSpan.FromSeconds(10));
 
         middleware.StartResponse(context);
-        Assert.NotEqual(clock.UtcNow, context.ResponseTime);
+        Assert.NotEqual(timeProvider.GetUtcNow(), context.ResponseTime);
         Assert.Equal(initialTime, context.ResponseTime);
     }
 
@@ -448,19 +442,16 @@ public class ResponseCachingMiddlewareTests
     [Fact]
     public void FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.MinValue
-        };
+        var timeProvider = new TestTimeProvider(DateTimeOffset.MinValue);
         var sink = new TestSink();
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.ResponseTime = timeProvider.GetUtcNow();
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 
@@ -471,24 +462,21 @@ public class ResponseCachingMiddlewareTests
     [Fact]
     public void FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var sink = new TestSink();
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
+        context.ResponseTime = timeProvider.GetUtcNow();
         context.HttpContext.Response.Headers.CacheControl = new CacheControlHeaderValue()
         {
             MaxAge = TimeSpan.FromSeconds(12)
         }.ToString();
 
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 
@@ -499,24 +487,21 @@ public class ResponseCachingMiddlewareTests
     [Fact]
     public void FinalizeCacheHeadersAsync_ResponseValidity_UseSharedMaxAgeIfAvailable()
     {
-        var clock = new TestClock
-        {
-            UtcNow = DateTimeOffset.UtcNow
-        };
+        var timeProvider = new TestTimeProvider();
         var sink = new TestSink();
         var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions
         {
-            SystemClock = clock
+            TimeProvider = timeProvider
         });
         var context = TestUtils.CreateTestContext();
 
-        context.ResponseTime = clock.UtcNow;
+        context.ResponseTime = timeProvider.GetUtcNow();
         context.HttpContext.Response.Headers.CacheControl = new CacheControlHeaderValue()
         {
             MaxAge = TimeSpan.FromSeconds(12),
             SharedMaxAge = TimeSpan.FromSeconds(13)
         }.ToString();
-        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(clock.UtcNow + TimeSpan.FromSeconds(11));
+        context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11));
 
         middleware.FinalizeCacheHeaders(context);
 

+ 17 - 3
src/Middleware/ResponseCaching/test/TestUtils.cs

@@ -172,7 +172,7 @@ internal class TestUtils
                             {
                                 responseCachingOptions.MaximumBodySize = options.MaximumBodySize;
                                 responseCachingOptions.UseCaseSensitivePaths = options.UseCaseSensitivePaths;
-                                responseCachingOptions.SystemClock = options.SystemClock;
+                                responseCachingOptions.TimeProvider = options.TimeProvider;
                             }
                         });
                     })
@@ -390,7 +390,21 @@ internal class TestResponseCache : IResponseCache
     }
 }
 
-internal class TestClock : ISystemClock
+internal class TestTimeProvider : TimeProvider
 {
-    public DateTimeOffset UtcNow { get; set; }
+    private DateTimeOffset _current;
+
+    public TestTimeProvider() : this(DateTimeOffset.UtcNow) { }
+
+    public TestTimeProvider(DateTimeOffset current)
+    {
+        _current = current;
+    }
+
+    public override DateTimeOffset GetUtcNow() => _current;
+
+    public void Advance(TimeSpan timeSpan)
+    {
+        _current += timeSpan;
+    }
 }