| File: NativeApplicationLifetimeTests.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Native.Core.Tests\Aspire.Hosting.Native.Core.Tests.csproj (Aspire.Hosting.Native.Core.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.Native.Api; using Aspire.Hosting.Native.Rpc; using Microsoft.Extensions.Time.Testing; namespace Aspire.Hosting.Native.Core.Tests; public class NativeApplicationLifetimeTests { [Fact] public async Task StopWithoutCooperativeGuestCompletesImmediately() { using var lifetime = new NativeApplicationLifetime(); await lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("No guest deadline should run."), CancellationToken.None); Assert.Throws<InvalidOperationException>(lifetime.CreateRun); } [Fact] public async Task CooperativeGuestReceivesStopBeforeTransportShutdownAndHasBoundedCleanup() { var clock = new FakeTimeProvider(); using var lifetime = new NativeApplicationLifetime(); var run = lifetime.CreateRun(); var timedOut = false; var stopping = lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), clock, () => timedOut = true, CancellationToken.None); await run.WaitForStop().WaitAsync(TimeSpan.FromSeconds(10)); Assert.False(stopping.IsCompleted); clock.Advance(TimeSpan.FromSeconds(5)); await stopping.WaitAsync(TimeSpan.FromSeconds(10)); Assert.True(timedOut); run.Close(); } [Fact] public async Task ConnectionOwnedLifetimeDrainsAfterGuestCleanup() { using var lifetime = new NativeApplicationLifetime(); using var handles = new NativeHandles(4); var run = lifetime.CreateRun(); handles.Add(run, "run"); var stopping = lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("Guest already finished."), CancellationToken.None); await run.WaitForStop(); Assert.False(stopping.IsCompleted); handles.Dispose(); await stopping.WaitAsync(TimeSpan.FromSeconds(10)); run.Close(); Assert.True(run.IsRevoked); } [Fact] public async Task DisposalCancelsPendingWaitersAndIsIdempotent() { var lifetime = new NativeApplicationLifetime(); var requested = lifetime.CreateRun().WaitForStop(); lifetime.Dispose(); lifetime.Dispose(); await Assert.ThrowsAnyAsync<OperationCanceledException>(() => requested); Assert.Throws<ObjectDisposedException>(lifetime.CreateRun); await lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("Disposed lifetime must not report a deadline."), CancellationToken.None); } [Fact] public async Task DisconnectedGuestDoesNotDelaySubsequentShutdown() { using var lifetime = new NativeApplicationLifetime(); var run = lifetime.CreateRun(); var pending = run.WaitForStop(); run.Close(); run.Close(); await Assert.ThrowsAnyAsync<OperationCanceledException>(() => pending); await lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("Disconnected guest must not delay shutdown."), CancellationToken.None); } [Fact] public async Task RejectedRunHandleDoesNotDelayShutdown() { using var lifetime = new NativeApplicationLifetime(); using var handles = new NativeHandles(0); var run = lifetime.CreateRun(); Assert.Throws<RpcFault>(() => handles.Add(run, "run")); Assert.True(run.IsRevoked); await lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("Rejected handle must not retain guest ownership."), CancellationToken.None); } [Fact] public async Task AllRegisteredGuestsMustReleaseBeforeShutdownCompletes() { using var lifetime = new NativeApplicationLifetime(); var first = lifetime.CreateRun(); var second = lifetime.CreateRun(); var stopping = lifetime.RequestStopAsync(TimeSpan.FromSeconds(5), TimeProvider.System, () => Assert.Fail("Guests already finished."), CancellationToken.None); first.Close(); Assert.False(stopping.IsCompleted); second.Close(); await stopping.WaitAsync(TimeSpan.FromSeconds(10)); } }