| File: Publishing\ContainerRuntimeReadinessTests.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable ASPIRECONTAINERRUNTIME001 using Aspire.Hosting.Publishing; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Time.Testing; namespace Aspire.Hosting.Tests.Publishing; public class ContainerRuntimeReadinessTests { [Fact] public async Task HealthyRuntimeDoesNotPrompt() { var interactions = new TestInteractionService(); using var services = CreateServices(interactions, TimeProvider.System); var runtime = new FakeContainerRuntime(name: "Docker"); await services.GetRequiredService<ContainerRuntimeReadiness>() .EnsureRunningAsync(runtime, TestContext.Current.CancellationToken).DefaultTimeout(); Assert.Equal(1, runtime.CheckIfRunningCallCount); Assert.False(interactions.Interactions.Reader.TryRead(out _)); } [Fact] public async Task UnavailableInteractionFailsWithActionableError() { var interactions = new TestInteractionService { IsAvailable = false }; using var services = CreateServices(interactions, TimeProvider.System); var runtime = new FakeContainerRuntime(isRunning: false, name: "Docker"); var exception = await Assert.ThrowsAsync<DistributedApplicationException>(() => services.GetRequiredService<ContainerRuntimeReadiness>() .EnsureRunningAsync(runtime, TestContext.Current.CancellationToken)).DefaultTimeout(); Assert.Equal("Docker is not running. Start Docker and try again.", exception.Message); Assert.Equal(1, runtime.CheckIfRunningCallCount); Assert.False(interactions.Interactions.Reader.TryRead(out _)); } [Fact] public async Task ConcurrentChecksShareRecoveryPrompt() { var interactions = new TestInteractionService(); using var services = CreateServices(interactions, TimeProvider.System); var running = 0; var runtime = new FakeContainerRuntime(name: "Docker") { CheckIfRunningAsyncCallback = _ => Task.FromResult(Volatile.Read(ref running) != 0) }; var readiness = services.GetRequiredService<ContainerRuntimeReadiness>(); var first = readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken); var interaction = await interactions.Interactions.Reader.ReadAsync(TestContext.Current.CancellationToken).AsTask().DefaultTimeout(); var second = readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken); Assert.Equal(1, runtime.CheckIfRunningCallCount); Assert.Equal("Docker is not running", interaction.Title); Assert.Equal("Start Docker and confirm to continue.", interaction.Message); Assert.Equal(MessageIntent.Warning, Assert.IsType<NotificationInteractionOptions>(interaction.Options).Intent); Assert.False(interactions.Interactions.Reader.TryRead(out _)); Interlocked.Exchange(ref running, 1); interaction.CompletionTcs.SetResult(InteractionResult.Ok(true)); await Task.WhenAll(first, second).DefaultTimeout(); Assert.Equal(2, runtime.CheckIfRunningCallCount); } [Theory] [InlineData(false)] [InlineData(true)] public async Task DeclinedRecoveryDoesNotCacheFailure(bool canceled) { var interactions = new TestInteractionService(); using var services = CreateServices(interactions, TimeProvider.System); var running = 0; var runtime = new FakeContainerRuntime(name: "Docker") { CheckIfRunningAsyncCallback = _ => Task.FromResult(Volatile.Read(ref running) != 0) }; var readiness = services.GetRequiredService<ContainerRuntimeReadiness>(); var check = readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken); var interaction = await interactions.Interactions.Reader.ReadAsync(TestContext.Current.CancellationToken).AsTask().DefaultTimeout(); interaction.CompletionTcs.SetResult(canceled ? InteractionResult.Cancel<bool>() : InteractionResult.Ok(false)); var exception = await Assert.ThrowsAsync<DistributedApplicationException>(() => check).DefaultTimeout(); Assert.Equal("Docker is not running. Start Docker and try again.", exception.Message); Interlocked.Exchange(ref running, 1); await readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken).DefaultTimeout(); Assert.Equal(2, runtime.CheckIfRunningCallCount); Assert.False(interactions.Interactions.Reader.TryRead(out _)); } [Fact] public async Task CompletedSuccessDoesNotHideStoppedRuntime() { var interactions = new TestInteractionService { IsAvailable = false }; using var services = CreateServices(interactions, TimeProvider.System); var running = 1; var runtime = new FakeContainerRuntime(name: "Docker") { CheckIfRunningAsyncCallback = _ => Task.FromResult(Volatile.Read(ref running) != 0) }; var readiness = services.GetRequiredService<ContainerRuntimeReadiness>(); await readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken).DefaultTimeout(); Interlocked.Exchange(ref running, 0); await Assert.ThrowsAsync<DistributedApplicationException>(() => readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken)).DefaultTimeout(); Assert.Equal(2, runtime.CheckIfRunningCallCount); } [Fact] public async Task CancelingAdditionalWaiterDoesNotCancelSharedRecovery() { var interactions = new TestInteractionService(); using var services = CreateServices(interactions, TimeProvider.System); var running = 0; var runtime = new FakeContainerRuntime(name: "Docker") { CheckIfRunningAsyncCallback = _ => Task.FromResult(Volatile.Read(ref running) != 0) }; var readiness = services.GetRequiredService<ContainerRuntimeReadiness>(); var first = readiness.EnsureRunningAsync(runtime, TestContext.Current.CancellationToken); var interaction = await interactions.Interactions.Reader.ReadAsync(TestContext.Current.CancellationToken).AsTask().DefaultTimeout(); using var cancellation = new CancellationTokenSource(); var second = readiness.EnsureRunningAsync(runtime, cancellation.Token); cancellation.Cancel(); await Assert.ThrowsAnyAsync<OperationCanceledException>(() => second).DefaultTimeout(); Assert.False(first.IsCompleted); Interlocked.Exchange(ref running, 1); interaction.CompletionTcs.SetResult(InteractionResult.Ok(true)); await first.DefaultTimeout(); Assert.Equal(2, runtime.CheckIfRunningCallCount); } [Fact] public async Task RecoveryTimeoutReportsUnavailableRuntime() { var interactions = new TestInteractionService(); var timeProvider = new FakeTimeProvider(); using var services = CreateServices(interactions, timeProvider); var polling = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var runtime = new FakeContainerRuntime(name: "Docker"); runtime.CheckIfRunningAsyncCallback = _ => { if (runtime.CheckIfRunningCallCount > 1) { polling.TrySetResult(); } return Task.FromResult(false); }; var check = services.GetRequiredService<ContainerRuntimeReadiness>() .EnsureRunningAsync(runtime, TestContext.Current.CancellationToken); var interaction = await interactions.Interactions.Reader.ReadAsync(TestContext.Current.CancellationToken).AsTask().DefaultTimeout(); interaction.CompletionTcs.SetResult(InteractionResult.Ok(true)); await polling.Task.DefaultTimeout(); timeProvider.Advance(TimeSpan.FromMinutes(5)); var exception = await Assert.ThrowsAsync<DistributedApplicationException>(() => check).DefaultTimeout(); Assert.Equal("Docker is not running. Start Docker and try again.", exception.Message); } private static ServiceProvider CreateServices(TestInteractionService interactions, TimeProvider timeProvider) { return new ServiceCollection() .AddSingleton<IInteractionService>(interactions) .AddSingleton(timeProvider) .AddSingleton<ContainerRuntimeReadiness>() .BuildServiceProvider(); } }