File: ShutdownTests.cs
Web Access
Project: src\src\Hosting\test\FunctionalTests\Microsoft.AspNetCore.Hosting.FunctionalTests.csproj (Microsoft.AspNetCore.Hosting.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.InternalTesting;
using Xunit.Abstractions;
 
namespace Microsoft.AspNetCore.Hosting.FunctionalTests;
 
public class ShutdownTests : LoggedTest
{
    private static readonly string StartedMessage = "Started";
    private static readonly string CompletionMessage = "Stopping firing\n" +
                                                        "Stopping end\n" +
                                                        "Stopped firing\n" +
                                                        "Stopped end";
 
    public ShutdownTests(ITestOutputHelper output) : base(output) { }
 
    [ConditionalFact]
    [OSSkipCondition(OperatingSystems.Windows)]
    [OSSkipCondition(OperatingSystems.MacOSX)]
    public async Task ShutdownTestRun()
    {
        await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run");
    }
 
    [ConditionalFact]
    [OSSkipCondition(OperatingSystems.Windows)]
    [OSSkipCondition(OperatingSystems.MacOSX)]
    public async Task ShutdownTestWaitForShutdown()
    {
        await ExecuteShutdownTest(nameof(ShutdownTestWaitForShutdown), "WaitForShutdown");
    }
 
    private async Task ExecuteShutdownTest(string testName, string shutdownMechanic)
    {
        using (StartLog(out var loggerFactory))
        {
            var logger = loggerFactory.CreateLogger(testName);
 
            // https://github.com/dotnet/aspnetcore/issues/8247
#pragma warning disable 0618
            var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets",
                "Microsoft.AspNetCore.Hosting.TestSites");
#pragma warning restore 0618
 
            var deploymentParameters = new DeploymentParameters(
                applicationPath,
                ServerType.Kestrel,
                RuntimeFlavor.CoreClr,
                RuntimeArchitectures.Current)
            {
                EnvironmentName = "Shutdown",
                TargetFramework = Tfm.Default,
                ApplicationType = ApplicationType.Portable,
                PublishApplicationBeforeDeployment = true,
                StatusMessagesEnabled = false
            };
 
            deploymentParameters.EnvironmentVariables["ASPNETCORE_STARTMECHANIC"] = shutdownMechanic;
 
            using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
            {
                var startedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
                var completedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
                var output = string.Empty;
 
                deployer.ProcessOutputListener = (data) =>
                {
                    if (!string.IsNullOrEmpty(data) && data.StartsWith(StartedMessage, StringComparison.Ordinal))
                    {
                        startedTcs.TrySetResult();
                        output += data.Substring(StartedMessage.Length) + '\n';
                    }
                    else
                    {
                        output += data + '\n';
                    }
 
                    if (output.Contains(CompletionMessage))
                    {
                        completedTcs.TrySetResult();
                    }
                };
 
                await deployer.DeployAsync();
 
                try
                {
                    await startedTcs.Task.TimeoutAfter(TimeSpan.FromMinutes(1));
                }
                catch (TimeoutException ex)
                {
                    throw new InvalidOperationException("Timeout while waiting for host process to output started message.", ex);
                }
 
                SendSIGINT(deployer.HostProcess.Id);
 
                WaitForExitOrKill(deployer.HostProcess);
 
                try
                {
                    await completedTcs.Task.TimeoutAfter(TimeSpan.FromMinutes(1));
                }
                catch (TimeoutException ex)
                {
                    throw new InvalidOperationException($"Timeout while waiting for host process to output completion message. The received output is: {output}", ex);
                }
 
                output = output.Trim('\n');
 
                Assert.Equal(CompletionMessage, output);
            }
        }
    }
 
    private static void SendSIGINT(int processId)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = "kill",
            Arguments = processId.ToString(CultureInfo.InvariantCulture),
            RedirectStandardOutput = true,
            UseShellExecute = false
        };
 
        var process = Process.Start(startInfo);
        WaitForExitOrKill(process);
    }
 
    private static void WaitForExitOrKill(Process process)
    {
        process.WaitForExit(5 * 1000);
        if (!process.HasExited)
        {
            process.Kill();
        }
 
        Assert.Equal(0, process.ExitCode);
    }
}