| File: Utils\AsyncTestHelpersTests.cs | Web Access |
| Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.CompilerServices; using Aspire.Cli.Tests.TestServices; using Microsoft.AspNetCore.InternalTesting; namespace Aspire.Cli.Tests.Utils; public class AsyncTestHelpersTests { [Fact] public async Task WithDisposeTimeout_CreatesAndDisposesInstance() { var instance = new TestAsyncDisposable(() => ValueTask.CompletedTask); var factoryCalls = 0; await using (var lifetime = AsyncTestHelpers.WithDisposeTimeout(() => { factoryCalls++; return instance; })) { Assert.Same(instance, lifetime.Instance); Assert.Equal(1, factoryCalls); Assert.Equal(0, instance.DisposeCount); } Assert.Equal(1, factoryCalls); Assert.Equal(1, instance.DisposeCount); } [Fact] public async Task WithDisposeTimeout_WaitsForAsynchronousDisposal() { var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var instance = new TestAsyncDisposable(async () => { started.TrySetResult(); await release.Task.ConfigureAwait(false); }); var lifetime = AsyncTestHelpers.WithDisposeTimeout(() => instance); var disposal = lifetime.DisposeAsync().AsTask(); try { await started.Task.DefaultTimeout(); Assert.False(disposal.IsCompleted); } finally { release.TrySetResult(); await disposal.DefaultTimeout(); } Assert.Equal(1, instance.DisposeCount); } [Theory] [InlineData(false)] [InlineData(true)] public async Task WithDisposeTimeout_PropagatesDisposalException(bool synchronous) { var failure = new InvalidOperationException("Disposal failed."); var instance = new TestAsyncDisposable(() => synchronous ? throw failure : ValueTask.FromException(failure)); var lifetime = AsyncTestHelpers.WithDisposeTimeout(() => instance); var exception = await Assert.ThrowsAsync<InvalidOperationException>( () => lifetime.DisposeAsync().AsTask()).DefaultTimeout(); Assert.Same(failure, exception); Assert.Equal(1, instance.DisposeCount); } [Theory] [InlineData(false)] [InlineData(true)] public async Task WithDisposeTimeout_TimesOutBlockedDisposal(bool synchronous) { if (Debugger.IsAttached) { Assert.Skip("Disposal timeouts are disabled while a debugger is attached."); } var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var instance = new TestAsyncDisposable(async () => { started.TrySetResult(); try { if (synchronous) { release.Task.GetAwaiter().GetResult(); } else { await release.Task.ConfigureAwait(false); } } finally { completed.TrySetResult(); } }); var timeout = TimeSpan.FromMilliseconds(100); var caller = GetCallerInfo(); var lifetime = AsyncTestHelpers.WithDisposeTimeout(() => instance, timeout); // Keep this regression test bounded even if the wrapper stops offloading synchronous disposal. var disposal = Task.Run(() => lifetime.DisposeAsync().AsTask()); try { await started.Task.DefaultTimeout(); var exception = await Assert.ThrowsAsync<TimeoutException>(() => disposal).DefaultTimeout(); Assert.Equal($"The operation at {caller.FilePath}:{caller.LineNumber + 1} timed out after reaching the limit of {timeout.TotalMilliseconds}ms.", exception.Message); Assert.False(completed.Task.IsCompleted); Assert.Equal(1, instance.DisposeCount); } finally { release.TrySetResult(); await completed.Task.DefaultTimeout(); } } [Theory] [InlineData(0)] [InlineData(-1)] public void WithDisposeTimeout_InvalidTimeout_DoesNotCreateInstance(int milliseconds) { var factoryCalls = 0; var exception = Assert.Throws<ArgumentOutOfRangeException>(() => AsyncTestHelpers.WithDisposeTimeout(() => { factoryCalls++; return new TestAsyncDisposable(() => ValueTask.CompletedTask); }, TimeSpan.FromMilliseconds(milliseconds))); Assert.Equal("timeout", exception.ParamName); Assert.Equal(0, factoryCalls); } [Fact] public void WithDisposeTimeout_PropagatesFactoryException() { var failure = new InvalidOperationException("Creation failed."); var exception = Assert.Throws<InvalidOperationException>(() => AsyncTestHelpers.WithDisposeTimeout<TestAsyncDisposable>(() => throw failure)); Assert.Same(failure, exception); } private static (string FilePath, int LineNumber) GetCallerInfo( [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = default) => (filePath, lineNumber); }