// 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(); Assert.Equal("WithHostingEnvironment", env.EnvironmentName); } } [Fact] public void CanCreateApplicationServicesWithAddedServices() { using (var host = CreateBuilder().ConfigureServices((hostContext, services) => services.AddSingleton()).Build()) { Assert.NotNull(host.Services.GetRequiredService()); } } [Fact] public void EnvDefaultsToProductionIfNoConfig() { using (var host = CreateBuilder().Build()) { var env = host.Services.GetService(); Assert.Equal(EnvironmentName.Production, env.EnvironmentName); } } [Fact] public void EnvDefaultsToConfigValueIfSpecified() { var vals = new Dictionary { { "Environment", EnvironmentName.Staging } }; var builder = new ConfigurationBuilder() .AddInMemoryCollection(vals); var config = builder.Build(); using (var host = CreateBuilder(config).Build()) { var env = host.Services.GetService(); 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(); 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(); }) .Start()) { service = (FakeHostedService)host.Services.GetRequiredService(); 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(); }) .Build()) { var lifetime = host.Services.GetRequiredService(); lifetime.StopApplication(); var svc = (TestHostedService)host.Services.GetRequiredService(); 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(); }) .Build()) { var svc = (TestHostedService)host.Services.GetRequiredService(); 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(() => 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(_ => 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(() => startTask); Assert.NotNull(host); service = (FakeHostedService)host.Services.GetRequiredService(); 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(_ => new FakeHostedService() { StartAction = ct => { serviceStarting.Set(); } }); services.AddSingleton(_ => 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(); Assert.Equal(1, service.StartCount); Assert.Equal(0, service.StopCount); Assert.Equal(0, service.DisposeCount); lifetime = (FakeHostLifetime)host.Services.GetRequiredService(); 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(_ => new FakeHostedService() { StartAction = ct => { serviceStarting.Set(); } }); services.AddSingleton(_ => 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(() => startTask); Assert.False(serviceStarting.WaitOne(0)); lifetimeContinue.Set(); Assert.False(serviceStarting.WaitOne(0)); Assert.NotNull(host); service = (FakeHostedService)host.Services.GetRequiredService(); Assert.Equal(0, service.StartCount); Assert.Equal(0, service.StopCount); Assert.Equal(0, service.DisposeCount); lifetime = (FakeHostLifetime)host.Services.GetRequiredService(); 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 stoppingAction = null; object stoppingState = null; FakeHostedService service; FakeHostLifetime lifetime; using (var host = CreateBuilder() .ConfigureServices((services) => { services.AddSingleton(); services.AddSingleton(_ => 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(); stoppingAction(stoppingState); Assert.True(appLifetime.ApplicationStopping.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))); service = (FakeHostedService)host.Services.GetRequiredService(); Assert.Equal(1, service.StartCount); Assert.Equal(0, service.StopCount); Assert.Equal(0, service.DisposeCount); lifetime = (FakeHostLifetime)host.Services.GetRequiredService(); 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(); services.AddSingleton(_ => new FakeHostLifetime() { StartAction = (callback, state) => callback(state), }); }) .Build()) { await host.StartAsync(); service = (FakeHostedService)host.Services.GetRequiredService(); Assert.Equal(1, service.StartCount); Assert.Equal(0, service.StopCount); Assert.Equal(0, service.DisposeCount); lifetime = (FakeHostLifetime)host.Services.GetRequiredService(); 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()) .Build()) { var lifetime = host.Services.GetRequiredService(); service = (FakeHostedService)host.Services.GetRequiredService(); 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(); service.Setup(s => s.StopAsync(It.IsAny())) .Returns(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(); 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(); s.AddSingleton(); }) .Build()) { await host.StartAsync(); var singleton = (FakeService)host.Services.GetService(); var transient = (FakeService)host.Services.GetService(); 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(); 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(); 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(_ => new DelegateHostedService(started, stopping, disposing)); }) .Build()) { var lifetime = host.Services.GetRequiredService(); 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(); }) .Build()) { var lifetime = host.Services.GetRequiredService(); lifetime.StopApplication(); await host.StartAsync(); var svc = (TestHostedService)host.Services.GetRequiredService(); 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(_ => new DelegateHostedService(started, stopping, disposing)); }) .Build()) { var lifetime = host.Services.GetRequiredService(); 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(_ => new DelegateHostedService(started, stopping, disposing)); }) .Build()) { var lifetime = host.Services.GetRequiredService(); 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(); var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted); var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping); await Assert.ThrowsAsync(() => 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(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(); } } }