936 lines
34 KiB
C#
936 lines
34 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting.Fakes;
|
|
using Microsoft.Extensions.Hosting.Tests.Fakes;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.Extensions.Hosting
|
|
{
|
|
public class HostTests
|
|
{
|
|
[Fact]
|
|
public async Task HostInjectsHostingEnvironment()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.UseEnvironment("WithHostingEnvironment")
|
|
.Build())
|
|
{
|
|
await host.StartAsync();
|
|
var env = host.Services.GetService<IHostingEnvironment>();
|
|
Assert.Equal("WithHostingEnvironment", env.EnvironmentName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void CanCreateApplicationServicesWithAddedServices()
|
|
{
|
|
using (var host = CreateBuilder().ConfigureServices((hostContext, services) => services.AddSingleton<IFakeService, FakeService>()).Build())
|
|
{
|
|
Assert.NotNull(host.Services.GetRequiredService<IFakeService>());
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvDefaultsToProductionIfNoConfig()
|
|
{
|
|
using (var host = CreateBuilder().Build())
|
|
{
|
|
var env = host.Services.GetService<IHostingEnvironment>();
|
|
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvDefaultsToConfigValueIfSpecified()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "Environment", EnvironmentName.Staging }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
|
|
using (var host = CreateBuilder(config).Build())
|
|
{
|
|
var env = host.Services.GetService<IHostingEnvironment>();
|
|
Assert.Equal(EnvironmentName.Staging, env.EnvironmentName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsEnvironment_Extension_Is_Case_Insensitive()
|
|
{
|
|
using (var host = CreateBuilder().Build())
|
|
{
|
|
await host.StartAsync();
|
|
var env = host.Services.GetRequiredService<IHostingEnvironment>();
|
|
Assert.True(env.IsEnvironment(EnvironmentName.Production));
|
|
Assert.True(env.IsEnvironment("producTion"));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostCanBeStarted()
|
|
{
|
|
FakeHostedService service;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((hostContext, services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, FakeHostedService>();
|
|
})
|
|
.Start())
|
|
{
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.NotNull(host);
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
}
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostedServiceStartNotCalledIfHostNotStarted()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((hostContext, services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, TestHostedService>();
|
|
})
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
lifetime.StopApplication();
|
|
|
|
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.False(svc.StartCalled);
|
|
await host.StopAsync();
|
|
Assert.False(svc.StopCalled);
|
|
host.Dispose();
|
|
Assert.False(svc.StopCalled);
|
|
Assert.True(svc.DisposeCalled);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostCanBeStoppedWhenNotStarted()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((hostContext, services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, TestHostedService>();
|
|
})
|
|
.Build())
|
|
{
|
|
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.False(svc.StartCalled);
|
|
await host.StopAsync();
|
|
Assert.False(svc.StopCalled);
|
|
host.Dispose();
|
|
Assert.False(svc.StopCalled);
|
|
Assert.True(svc.DisposeCalled);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AppCrashesOnStartWhenFirstHostedServiceThrows()
|
|
{
|
|
bool[] events1 = null;
|
|
bool[] events2 = null;
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
events1 = RegisterCallbacksThatThrow(services);
|
|
events2 = RegisterCallbacksThatThrow(services);
|
|
})
|
|
.Build())
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
|
|
Assert.True(events1[0]);
|
|
Assert.False(events2[0]);
|
|
host.Dispose();
|
|
// Stopping
|
|
Assert.False(events1[1]);
|
|
Assert.False(events2[1]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartCanBeCancelled()
|
|
{
|
|
var serviceStarting = new ManualResetEvent(false);
|
|
var startCancelled = new ManualResetEvent(false);
|
|
FakeHostedService service;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
|
{
|
|
StartAction = ct =>
|
|
{
|
|
Assert.False(ct.IsCancellationRequested);
|
|
serviceStarting.Set();
|
|
Assert.True(startCancelled.WaitOne(TimeSpan.FromSeconds(5)));
|
|
ct.ThrowIfCancellationRequested();
|
|
}
|
|
});
|
|
})
|
|
.Build())
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
|
|
var startTask = Task.Run(() => host.StartAsync(cts.Token));
|
|
Assert.True(serviceStarting.WaitOne(TimeSpan.FromSeconds(5)));
|
|
cts.Cancel();
|
|
startCancelled.Set();
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() => startTask);
|
|
|
|
Assert.NotNull(host);
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
}
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostLifetimeOnStartedDelaysStart()
|
|
{
|
|
var serviceStarting = new ManualResetEvent(false);
|
|
var lifetimeStart = new ManualResetEvent(false);
|
|
var lifetimeContinue = new ManualResetEvent(false);
|
|
FakeHostedService service;
|
|
FakeHostLifetime lifetime;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
|
{
|
|
StartAction = ct =>
|
|
{
|
|
serviceStarting.Set();
|
|
}
|
|
});
|
|
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
|
{
|
|
StartAction = (callback, state) =>
|
|
{
|
|
lifetimeStart.Set();
|
|
Assert.True(lifetimeContinue.WaitOne(TimeSpan.FromSeconds(5)));
|
|
callback(state);
|
|
}
|
|
});
|
|
})
|
|
.Build())
|
|
{
|
|
var startTask = Task.Run(() => host.StartAsync());
|
|
Assert.True(lifetimeStart.WaitOne(TimeSpan.FromSeconds(5)));
|
|
Assert.False(serviceStarting.WaitOne(0));
|
|
|
|
lifetimeContinue.Set();
|
|
Assert.True(serviceStarting.WaitOne(TimeSpan.FromSeconds(5)));
|
|
|
|
await startTask;
|
|
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostLifetimeOnStartedCanBeCancelled()
|
|
{
|
|
var serviceStarting = new ManualResetEvent(false);
|
|
var lifetimeStart = new ManualResetEvent(false);
|
|
var lifetimeContinue = new ManualResetEvent(false);
|
|
FakeHostedService service;
|
|
FakeHostLifetime lifetime;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService>(_ => new FakeHostedService()
|
|
{
|
|
StartAction = ct =>
|
|
{
|
|
serviceStarting.Set();
|
|
}
|
|
});
|
|
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
|
{
|
|
StartAction = (callback, state) =>
|
|
{
|
|
lifetimeStart.Set();
|
|
}
|
|
});
|
|
})
|
|
.Build())
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
|
|
var startTask = Task.Run(() => host.StartAsync(cts.Token));
|
|
|
|
Assert.True(lifetimeStart.WaitOne(TimeSpan.FromSeconds(5)));
|
|
Assert.False(serviceStarting.WaitOne(0));
|
|
|
|
cts.Cancel();
|
|
await Assert.ThrowsAsync<TaskCanceledException>(() => startTask);
|
|
Assert.False(serviceStarting.WaitOne(0));
|
|
|
|
lifetimeContinue.Set();
|
|
Assert.False(serviceStarting.WaitOne(0));
|
|
|
|
Assert.NotNull(host);
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.Equal(0, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
Assert.Equal(0, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostLifetimeOnStoppingTriggersIApplicationLifetime()
|
|
{
|
|
var lifetimeRegistered = new ManualResetEvent(false);
|
|
Action<object> stoppingAction = null;
|
|
object stoppingState = null;
|
|
FakeHostedService service;
|
|
FakeHostLifetime lifetime;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, FakeHostedService>();
|
|
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
|
{
|
|
StartAction = (callback, state) => callback(state),
|
|
StoppingAction = (callback, state) =>
|
|
{
|
|
stoppingAction = callback;
|
|
stoppingState = state;
|
|
lifetimeRegistered.Set();
|
|
}
|
|
});
|
|
})
|
|
.Build())
|
|
{
|
|
await host.StartAsync();
|
|
Assert.True(lifetimeRegistered.WaitOne(0));
|
|
|
|
var appLifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
|
|
stoppingAction(stoppingState);
|
|
|
|
Assert.True(appLifetime.ApplicationStopping.WaitHandle.WaitOne(TimeSpan.FromSeconds(5)));
|
|
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostStopAsyncCallsHostLifetimeStopAsync()
|
|
{
|
|
FakeHostedService service;
|
|
FakeHostLifetime lifetime;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, FakeHostedService>();
|
|
services.AddSingleton<IHostLifetime>(_ => new FakeHostLifetime()
|
|
{
|
|
StartAction = (callback, state) => callback(state),
|
|
});
|
|
})
|
|
.Build())
|
|
{
|
|
await host.StartAsync();
|
|
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
lifetime = (FakeHostLifetime)host.Services.GetRequiredService<IHostLifetime>();
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(0, lifetime.StopCount);
|
|
|
|
await host.StopAsync();
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(1, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(1, lifetime.StopCount);
|
|
}
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(1, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
|
|
Assert.Equal(1, lifetime.StartCount);
|
|
Assert.Equal(1, lifetime.StoppingCount);
|
|
Assert.Equal(1, lifetime.StopCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostShutsDownWhenTokenTriggers()
|
|
{
|
|
FakeHostedService service;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) => services.AddSingleton<IHostedService, FakeHostedService>())
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
service = (FakeHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
|
|
var cts = new CancellationTokenSource();
|
|
|
|
var runInBackground = host.RunAsync(cts.Token);
|
|
|
|
// Wait on the host to be started
|
|
lifetime.ApplicationStarted.WaitHandle.WaitOne();
|
|
|
|
Assert.Equal(1, service.StartCount);
|
|
Assert.Equal(0, service.StopCount);
|
|
Assert.Equal(0, service.DisposeCount);
|
|
|
|
cts.Cancel();
|
|
|
|
// Wait on the host to shutdown
|
|
lifetime.ApplicationStopped.WaitHandle.WaitOne();
|
|
|
|
// Wait for RunAsync to finish to guarantee Disposal of Host
|
|
await runInBackground;
|
|
|
|
Assert.Equal(1, service.StopCount);
|
|
Assert.Equal(1, service.DisposeCount);
|
|
}
|
|
Assert.Equal(1, service.DisposeCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostStopAsyncCanBeCancelledEarly()
|
|
{
|
|
var service = new Mock<IHostedService>();
|
|
service.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
|
|
.Returns<CancellationToken>(token =>
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
token.WaitHandle.WaitOne();
|
|
});
|
|
});
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton(service.Object);
|
|
})
|
|
.Build())
|
|
{
|
|
await host.StartAsync();
|
|
|
|
var cts = new CancellationTokenSource();
|
|
|
|
var task = host.StopAsync(cts.Token);
|
|
cts.Cancel();
|
|
|
|
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(8))));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
var applicationStartedEvent = new ManualResetEventSlim(false);
|
|
var applicationStoppingEvent = new ManualResetEventSlim(false);
|
|
var applicationStoppedEvent = new ManualResetEventSlim(false);
|
|
var applicationStartedCompletedBeforeApplicationStopping = false;
|
|
var applicationStoppingCompletedBeforeApplicationStopped = false;
|
|
var applicationStoppedCompletedBeforeRunCompleted = false;
|
|
|
|
lifetime.ApplicationStarted.Register(() =>
|
|
{
|
|
applicationStartedEvent.Set();
|
|
});
|
|
|
|
lifetime.ApplicationStopping.Register(() =>
|
|
{
|
|
// Check whether the applicationStartedEvent has been set
|
|
applicationStartedCompletedBeforeApplicationStopping = applicationStartedEvent.IsSet;
|
|
|
|
// Simulate work.
|
|
Thread.Sleep(1000);
|
|
|
|
applicationStoppingEvent.Set();
|
|
});
|
|
|
|
lifetime.ApplicationStopped.Register(() =>
|
|
{
|
|
// Check whether the applicationStoppingEvent has been set
|
|
applicationStoppingCompletedBeforeApplicationStopped = applicationStoppingEvent.IsSet;
|
|
applicationStoppedEvent.Set();
|
|
});
|
|
|
|
var runHostAndVerifyApplicationStopped = Task.Run(async () =>
|
|
{
|
|
await host.RunAsync();
|
|
// Check whether the applicationStoppingEvent has been set
|
|
applicationStoppedCompletedBeforeRunCompleted = applicationStoppedEvent.IsSet;
|
|
});
|
|
|
|
// Wait until application has started to shut down the host
|
|
Assert.True(applicationStartedEvent.Wait(5000));
|
|
|
|
// Trigger host shutdown on a separate thread
|
|
Task.Run(() => lifetime.StopApplication());
|
|
|
|
// Wait for all events and host.Run() to complete
|
|
Assert.True(runHostAndVerifyApplicationStopped.Wait(5000));
|
|
|
|
// Verify Ordering
|
|
Assert.True(applicationStartedCompletedBeforeApplicationStopping);
|
|
Assert.True(applicationStoppingCompletedBeforeApplicationStopped);
|
|
Assert.True(applicationStoppedCompletedBeforeRunCompleted);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostDisposesServiceProvider()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((s) =>
|
|
{
|
|
s.AddTransient<IFakeService, FakeService>();
|
|
s.AddSingleton<IFakeSingletonService, FakeService>();
|
|
})
|
|
.Build())
|
|
{
|
|
await host.StartAsync();
|
|
|
|
var singleton = (FakeService)host.Services.GetService<IFakeSingletonService>();
|
|
var transient = (FakeService)host.Services.GetService<IFakeService>();
|
|
|
|
Assert.False(singleton.Disposed);
|
|
Assert.False(transient.Disposed);
|
|
|
|
await host.StopAsync();
|
|
|
|
Assert.False(singleton.Disposed);
|
|
Assert.False(transient.Disposed);
|
|
|
|
host.Dispose();
|
|
|
|
Assert.True(singleton.Disposed);
|
|
Assert.True(transient.Disposed);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostNotifiesApplicationStarted()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.Build())
|
|
{
|
|
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
|
|
|
Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
|
|
|
await host.StartAsync();
|
|
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostNotifiesAllIApplicationLifetimeCallbacksEvenIfTheyThrow()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.Build())
|
|
{
|
|
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
|
|
|
var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
|
|
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
|
|
var stopped = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopped);
|
|
|
|
await host.StartAsync();
|
|
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
|
Assert.True(started.All(s => s));
|
|
await host.StopAsync();
|
|
Assert.True(stopping.All(s => s));
|
|
host.Dispose();
|
|
Assert.True(stopped.All(s => s));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostStopApplicationDoesNotFireStopOnHostedService()
|
|
{
|
|
var stoppingCalls = 0;
|
|
var disposingCalls = 0;
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
Action started = () =>
|
|
{
|
|
};
|
|
|
|
Action stopping = () =>
|
|
{
|
|
stoppingCalls++;
|
|
};
|
|
|
|
Action disposing = () =>
|
|
{
|
|
disposingCalls++;
|
|
};
|
|
|
|
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
|
})
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
lifetime.StopApplication();
|
|
|
|
await host.StartAsync();
|
|
|
|
Assert.Equal(0, stoppingCalls);
|
|
Assert.Equal(0, disposingCalls);
|
|
}
|
|
Assert.Equal(0, stoppingCalls);
|
|
Assert.Equal(1, disposingCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostedServiceCanInjectApplicationLifetime()
|
|
{
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
services.AddSingleton<IHostedService, TestHostedService>();
|
|
})
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
lifetime.StopApplication();
|
|
|
|
await host.StartAsync();
|
|
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
|
|
Assert.True(svc.StartCalled);
|
|
|
|
await host.StopAsync();
|
|
Assert.True(svc.StopCalled);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostStopApplicationFiresStopOnHostedService()
|
|
{
|
|
var stoppingCalls = 0;
|
|
var startedCalls = 0;
|
|
var disposingCalls = 0;
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
Action started = () =>
|
|
{
|
|
startedCalls++;
|
|
};
|
|
|
|
Action stopping = () =>
|
|
{
|
|
stoppingCalls++;
|
|
};
|
|
|
|
Action disposing = () =>
|
|
{
|
|
disposingCalls++;
|
|
};
|
|
|
|
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
|
})
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
|
|
Assert.Equal(0, startedCalls);
|
|
|
|
await host.StartAsync();
|
|
Assert.Equal(1, startedCalls);
|
|
Assert.Equal(0, stoppingCalls);
|
|
Assert.Equal(0, disposingCalls);
|
|
|
|
await host.StopAsync();
|
|
|
|
Assert.Equal(1, startedCalls);
|
|
Assert.Equal(1, stoppingCalls);
|
|
Assert.Equal(0, disposingCalls);
|
|
|
|
host.Dispose();
|
|
|
|
Assert.Equal(1, startedCalls);
|
|
Assert.Equal(1, stoppingCalls);
|
|
Assert.Equal(1, disposingCalls);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostDisposeApplicationDoesNotFireStopOnHostedService()
|
|
{
|
|
var stoppingCalls = 0;
|
|
var startedCalls = 0;
|
|
var disposingCalls = 0;
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
Action started = () =>
|
|
{
|
|
startedCalls++;
|
|
};
|
|
|
|
Action stopping = () =>
|
|
{
|
|
stoppingCalls++;
|
|
};
|
|
|
|
Action disposing = () =>
|
|
{
|
|
disposingCalls++;
|
|
};
|
|
|
|
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
|
|
})
|
|
.Build())
|
|
{
|
|
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
|
|
|
Assert.Equal(0, startedCalls);
|
|
await host.StartAsync();
|
|
Assert.Equal(1, startedCalls);
|
|
Assert.Equal(0, stoppingCalls);
|
|
Assert.Equal(0, disposingCalls);
|
|
host.Dispose();
|
|
|
|
Assert.Equal(0, stoppingCalls);
|
|
Assert.Equal(1, disposingCalls);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HostDoesNotNotifyIApplicationLifetimeCallbacksIfIHostedServicesThrow()
|
|
{
|
|
bool[] events1 = null;
|
|
bool[] events2 = null;
|
|
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) =>
|
|
{
|
|
events1 = RegisterCallbacksThatThrow(services);
|
|
events2 = RegisterCallbacksThatThrow(services);
|
|
})
|
|
.Build())
|
|
{
|
|
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
|
|
|
|
var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
|
|
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
|
|
Assert.True(events1[0]);
|
|
Assert.False(events2[0]);
|
|
Assert.False(started.All(s => s));
|
|
host.Dispose();
|
|
Assert.False(events1[1]);
|
|
Assert.False(events2[1]);
|
|
Assert.False(stopping.All(s => s));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Host_InvokesConfigureServicesMethodsOnlyOnce()
|
|
{
|
|
int configureServicesCount = 0;
|
|
using (var host = CreateBuilder()
|
|
.ConfigureServices((services) => configureServicesCount++)
|
|
.Build())
|
|
{
|
|
Assert.Equal(1, configureServicesCount);
|
|
await host.StartAsync();
|
|
var services = host.Services;
|
|
var services2 = host.Services;
|
|
Assert.Equal(1, configureServicesCount);
|
|
}
|
|
}
|
|
|
|
private IHostBuilder CreateBuilder(IConfiguration config = null)
|
|
{
|
|
return new HostBuilder().ConfigureHostConfiguration(builder => builder.AddConfiguration(config ?? new ConfigurationBuilder().Build()));
|
|
}
|
|
|
|
private static bool[] RegisterCallbacksThatThrow(IServiceCollection services)
|
|
{
|
|
bool[] events = new bool[2];
|
|
|
|
Action started = () =>
|
|
{
|
|
events[0] = true;
|
|
throw new InvalidOperationException();
|
|
};
|
|
|
|
Action stopping = () =>
|
|
{
|
|
events[1] = true;
|
|
throw new InvalidOperationException();
|
|
};
|
|
|
|
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping, () => { }));
|
|
|
|
return events;
|
|
}
|
|
|
|
private static bool[] RegisterCallbacksThatThrow(CancellationToken token)
|
|
{
|
|
var signals = new bool[3];
|
|
for (int i = 0; i < signals.Length; i++)
|
|
{
|
|
token.Register(state =>
|
|
{
|
|
signals[(int)state] = true;
|
|
throw new InvalidOperationException();
|
|
}, i);
|
|
}
|
|
|
|
return signals;
|
|
}
|
|
|
|
private class TestHostedService : IHostedService, IDisposable
|
|
{
|
|
private readonly IApplicationLifetime _lifetime;
|
|
|
|
public TestHostedService(IApplicationLifetime lifetime)
|
|
{
|
|
_lifetime = lifetime;
|
|
}
|
|
|
|
public bool StartCalled { get; set; }
|
|
public bool StopCalled { get; set; }
|
|
public bool DisposeCalled { get; set; }
|
|
|
|
public Task StartAsync(CancellationToken token)
|
|
{
|
|
StartCalled = true;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken token)
|
|
{
|
|
StopCalled = true;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
DisposeCalled = true;
|
|
}
|
|
}
|
|
|
|
private class DelegateHostedService : IHostedService, IDisposable
|
|
{
|
|
private readonly Action _started;
|
|
private readonly Action _stopping;
|
|
private readonly Action _disposing;
|
|
|
|
public DelegateHostedService(Action started, Action stopping, Action disposing)
|
|
{
|
|
_started = started;
|
|
_stopping = stopping;
|
|
_disposing = disposing;
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken token)
|
|
{
|
|
_started();
|
|
return Task.CompletedTask;
|
|
}
|
|
public Task StopAsync(CancellationToken token)
|
|
{
|
|
_stopping();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void Dispose() => _disposing();
|
|
}
|
|
}
|
|
} |