forked from microsoft/aspire
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCliOrphanDetectorTests.cs
More file actions
131 lines (100 loc) · 5.37 KB
/
Copy pathCliOrphanDetectorTests.cs
File metadata and controls
131 lines (100 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading.Channels;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Cli;
using Aspire.Hosting.Utils;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Time.Testing;
namespace Aspire.Cli.Tests;
public class CliOrphanDetectorTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public async Task CliOrphanDetectorCompletesWhenNoPidEnvironmentVariablePresent()
{
var configuration = new ConfigurationBuilder().Build();
var lifetime = new HostLifetimeStub(() => {});
var detector = new CliOrphanDetector(configuration, lifetime, TimeProvider.System);
// The detector should complete almost immediately because there is no
// environment variable present that indicates that it is hitched to
// .NET Aspire lifetime.
await detector.StartAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
}
[Fact]
public async Task CliOrphanDetectorCallsStopIfEnvironmentVariablePresentAndProcessNotRunning()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> { { "ASPIRE_CLI_PID", "1111" } })
.Build();
var stopSignalChannel = Channel.CreateUnbounded<bool>();
var lifetime = new HostLifetimeStub(() => stopSignalChannel.Writer.TryWrite(true));
var detector = new CliOrphanDetector(configuration, lifetime, TimeProvider.System);
detector.IsProcessRunning = _ => false;
// The detector should complete almost immediately because there is no
// environment variable present that indicates that it is hitched to
// .NET Aspire lifetime.
await detector.StartAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
Assert.True(await stopSignalChannel.Reader.WaitToReadAsync());
}
[Fact]
public async Task CliOrphanDetectorAfterTheProcessWasRunningForAWhileThenStops()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> { { "ASPIRE_CLI_PID", "1111" } })
.Build();
var fakeTimeProvider = new FakeTimeProvider(DateTimeOffset.Now);
var stopSignalChannel = Channel.CreateUnbounded<bool>();
var processRunningChannel = Channel.CreateUnbounded<int>();
var lifetime = new HostLifetimeStub(() => stopSignalChannel.Writer.TryWrite(true));
var detector = new CliOrphanDetector(configuration, lifetime, fakeTimeProvider);
var processRunningCallCounter = 0;
detector.IsProcessRunning = pid => {
Assert.True(processRunningChannel.Writer.TryWrite(++processRunningCallCounter));
return processRunningCallCounter < 5;
};
// The detector should complete after about 5 seconds
await detector.StartAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
Assert.True(await processRunningChannel.Reader.WaitToReadAsync());
fakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
Assert.True(await processRunningChannel.Reader.WaitToReadAsync());
fakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
Assert.True(await processRunningChannel.Reader.WaitToReadAsync());
fakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
Assert.True(await processRunningChannel.Reader.WaitToReadAsync());
fakeTimeProvider.Advance(TimeSpan.FromSeconds(1));
Assert.True(await processRunningChannel.Reader.WaitToReadAsync());
Assert.Equal(5, processRunningCallCounter);
Assert.True(await stopSignalChannel.Reader.WaitToReadAsync());
}
[Fact]
public async Task AppHostExitsWhenCliProcessPidDies()
{
using var fakeCliProcess = RemoteExecutor.Invoke(
static () => Thread.Sleep(Timeout.Infinite),
new RemoteInvokeOptions { CheckExitCode = false }
);
using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper);
builder.Configuration["ASPIRE_CLI_PID"] = fakeCliProcess.Process.Id.ToString();
var resourcesCreatedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
builder.Eventing.Subscribe<AfterResourcesCreatedEvent>((e, ct) => {
resourcesCreatedTcs.SetResult();
return Task.CompletedTask;
});
using var app = builder.Build();
var pendingRun = app.RunAsync();
// Wait until the apphost is spun up and then kill off the stub
// process so everything is torn down.
await resourcesCreatedTcs.Task.WaitAsync(TimeSpan.FromSeconds(60));
fakeCliProcess.Process.Kill();
await pendingRun.WaitAsync(TimeSpan.FromSeconds(60));
}
}
file sealed class HostLifetimeStub(Action stopImplementation) : IHostApplicationLifetime
{
public CancellationToken ApplicationStarted => throw new NotImplementedException();
public CancellationToken ApplicationStopped => throw new NotImplementedException();
public CancellationToken ApplicationStopping => throw new NotImplementedException();
public void StopApplication() => stopImplementation();
}