diff --git a/src/Hosting/Hosting.sln b/src/Hosting/Hosting.sln index a9667b0518..7f1103247a 100644 --- a/src/Hosting/Hosting.sln +++ b/src/Hosting/Hosting.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.TestHo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Hosting.WindowsServices", "WindowsServices\src\Microsoft.AspNetCore.Hosting.WindowsServices.csproj", "{FA29445B-1BA7-448D-8ADF-56BF6D6633BB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Hosting.WindowsServices.Tests", "WindowsServices\test\Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj", "{CCD5D010-7E06-4209-ADD5-3B010A41DCF1}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{31587D24-F6B5-4A47-A962-47CA7FEA79D0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildWebHostInvalidSignature", "test\testassets\BuildWebHostInvalidSignature\BuildWebHostInvalidSignature.csproj", "{BF146375-AA6C-43F3-BF0A-DCA551892DF8}" @@ -89,6 +91,10 @@ Global {FA29445B-1BA7-448D-8ADF-56BF6D6633BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA29445B-1BA7-448D-8ADF-56BF6D6633BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA29445B-1BA7-448D-8ADF-56BF6D6633BB}.Release|Any CPU.Build.0 = Release|Any CPU + {CCD5D010-7E06-4209-ADD5-3B010A41DCF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCD5D010-7E06-4209-ADD5-3B010A41DCF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCD5D010-7E06-4209-ADD5-3B010A41DCF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCD5D010-7E06-4209-ADD5-3B010A41DCF1}.Release|Any CPU.Build.0 = Release|Any CPU {BF146375-AA6C-43F3-BF0A-DCA551892DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BF146375-AA6C-43F3-BF0A-DCA551892DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF146375-AA6C-43F3-BF0A-DCA551892DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Hosting/WindowsServices/src/Properties/AssemblyInfo.cs b/src/Hosting/WindowsServices/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..63d95d6dfb --- /dev/null +++ b/src/Hosting/WindowsServices/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Hosting.WindowsServices.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Hosting/WindowsServices/src/WebHostService.cs b/src/Hosting/WindowsServices/src/WebHostService.cs index f468d05fe3..016c877bc2 100644 --- a/src/Hosting/WindowsServices/src/WebHostService.cs +++ b/src/Hosting/WindowsServices/src/WebHostService.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel; using System.ServiceProcess; using Microsoft.Extensions.DependencyInjection; @@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Hosting.WindowsServices /// public class WebHostService : ServiceBase { - private IWebHost _host; + private readonly IWebHost _host; private bool _stopRequestedByWindows; /// @@ -24,14 +25,27 @@ namespace Microsoft.AspNetCore.Hosting.WindowsServices _host = host ?? throw new ArgumentNullException(nameof(host)); } + /// + /// This method is not intended for direct use. Its sole purpose is to allow + /// the service to be started by the tests. + /// + internal void Start() => OnStart(Array.Empty()); + protected sealed override void OnStart(string[] args) { OnStarting(args); + _host.Start(); + + OnStarted(); + + // Register callback for application stopping after we've + // started the service, because otherwise we might introduce unwanted + // race conditions. _host .Services .GetRequiredService() - .ApplicationStopped + .ApplicationStopping .Register(() => { if (!_stopRequestedByWindows) @@ -39,10 +53,6 @@ namespace Microsoft.AspNetCore.Hosting.WindowsServices Stop(); } }); - - _host.Start(); - - OnStarted(); } protected sealed override void OnStop() diff --git a/src/Hosting/WindowsServices/test/Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj b/src/Hosting/WindowsServices/test/Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj new file mode 100644 index 0000000000..5fecfdba0a --- /dev/null +++ b/src/Hosting/WindowsServices/test/Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0 + + + + + + + + \ No newline at end of file diff --git a/src/Hosting/WindowsServices/test/Properties/AssemblyInfo.cs b/src/Hosting/WindowsServices/test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1dc44dd221 --- /dev/null +++ b/src/Hosting/WindowsServices/test/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// 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 Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +[assembly: OSSkipCondition(OperatingSystems.MacOSX)] +[assembly: OSSkipCondition(OperatingSystems.Linux)] diff --git a/src/Hosting/WindowsServices/test/WebHostServiceTests.cs b/src/Hosting/WindowsServices/test/WebHostServiceTests.cs new file mode 100644 index 0000000000..df341fc830 --- /dev/null +++ b/src/Hosting/WindowsServices/test/WebHostServiceTests.cs @@ -0,0 +1,68 @@ +// 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.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.WindowsServices; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Hosting +{ + public class WebHostServiceTests + { + // Reasonable timeout for our test operations to complete. + private static readonly TimeSpan OperationTimeout = TimeSpan.FromSeconds( 5 ); + + [ConditionalFact] + public async Task StopBeforeServiceStarted() + { + var host = new WebHostBuilder().UseServer(new FakeServer()).Configure(x => { }).Build(); + var webHostService = new WebHostService(host); + var applicationLifetime = host.Services.GetRequiredService(); + + applicationLifetime.StopApplication(); + webHostService.Start(); + + await Assert.ThrowsAsync( + () => Task.Delay(OperationTimeout, applicationLifetime.ApplicationStopped)); + } + + [ConditionalFact] + public async Task StopAfterServiceStarted() + { + var host = new WebHostBuilder().UseServer( new FakeServer() ).Configure( x => { } ).Build(); + var webHostService = new WebHostService(host); + var applicationLifetime = host.Services.GetRequiredService(); + + webHostService.Start(); + applicationLifetime.StopApplication(); + + await Assert.ThrowsAsync( + () => Task.Delay(OperationTimeout, applicationLifetime.ApplicationStopped)); + } + + private sealed class FakeServer : IServer + { + IFeatureCollection IServer.Features { get; } + public RequestDelegate RequestDelegate { get; private set; } + + public void Dispose() { } + + public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) + { + RequestDelegate = ctx => throw new NotSupportedException(); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + } +}