File: WorkflowScripts\DeploymentTestScriptTests.cs
Web Access
Project: src\tests\Infrastructure.Tests\Infrastructure.Tests.csproj (Infrastructure.Tests)
// 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 Aspire.TestUtilities;
using Xunit;
 
namespace Infrastructure.Tests;
 
/// <summary>
/// Guards the command and exit-code behavior of the nightly Deployment E2E test step.
/// </summary>
public sealed class DeploymentTestScriptTests(ITestOutputHelper output) : IDisposable
{
    private static readonly string s_runnerPath = Path.Combine(
        RepoRoot.Path,
        ".github",
        "workflows",
        "run-deployment-test.sh");
 
    private readonly TemporaryWorkspace _workspace = TemporaryWorkspace.Create(output);
 
    [Theory]
    [InlineData(0, false)]   // all tests passed
    [InlineData(8, false)]   // MTP "zero tests ran": the only test was dynamically skipped
    [InlineData(2, true)]    // a test failed
    [InlineData(7, true)]    // the test host crashed
    [RequiresTools(["bash"])]
    public async Task RunsDeploymentTestAndMarksOnlyGenuineFailures(int testExitCode, bool expectFailure)
    {
        var outputFile = Path.Combine(_workspace.Path, "github-output.txt");
        var argumentsFile = Path.Combine(_workspace.Path, "dotnet-arguments.txt");
        var fakeDotnetScript = Path.Combine(_workspace.Path, "dotnet.sh");
        await File.WriteAllTextAsync(
            fakeDotnetScript,
            """
            #!/usr/bin/env bash
            printf '%s\n' "$@" > "$FAKE_DOTNET_ARGUMENTS"
            exit "$FAKE_DOTNET_EXIT_CODE"
            """);
 
        var resultsDirectory = Path.Combine(_workspace.Path, "test results");
        var result = await RunTestStepAsync(
            testExitCode,
            outputFile,
            argumentsFile,
            fakeDotnetScript,
            resultsDirectory);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            [
                "test",
                "--project",
                "tests/Aspire.Deployment.EndToEnd.Tests/Aspire.Deployment.EndToEnd.Tests.csproj",
                "-c",
                "Release",
                "--results-directory",
                resultsDirectory,
                "--",
                "--report-trx",
                "--report-trx-filename",
                "Deployment.EndToEnd-ExampleTests.trx",
                "--filter-not-trait",
                "quarantined=true",
                "--filter-class",
                "Aspire.ExampleTests",
            ],
            await File.ReadAllLinesAsync(argumentsFile));
 
        var stepOutputs = File.Exists(outputFile) ? await File.ReadAllTextAsync(outputFile) : string.Empty;
        if (expectFailure)
        {
            Assert.Equal($"test_failed=true{Environment.NewLine}", stepOutputs);
        }
        else
        {
            Assert.Equal(string.Empty, stepOutputs);
        }
    }
 
    private async Task<CommandResult> RunTestStepAsync(
        int testExitCode,
        string outputFile,
        string argumentsFile,
        string fakeDotnetScript,
        string resultsDirectory)
    {
        using var process = new Process();
        process.StartInfo.FileName = "bash";
        process.StartInfo.ArgumentList.Add(s_runnerPath);
        process.StartInfo.ArgumentList.Add(resultsDirectory);
        process.StartInfo.ArgumentList.Add("Deployment.EndToEnd-ExampleTests");
        process.StartInfo.ArgumentList.Add("--filter-class");
        process.StartInfo.ArgumentList.Add("Aspire.ExampleTests");
        process.StartInfo.WorkingDirectory = _workspace.Path;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.Environment["GITHUB_OUTPUT"] = outputFile;
        process.StartInfo.Environment["DOTNET_SCRIPT"] = fakeDotnetScript;
        process.StartInfo.Environment["FAKE_DOTNET_ARGUMENTS"] = argumentsFile;
        process.StartInfo.Environment["FAKE_DOTNET_EXIT_CODE"] = testExitCode.ToString();
 
        process.Start();
 
        // Read both streams concurrently to avoid deadlock when a pipe buffer fills.
        var stdoutTask = process.StandardOutput.ReadToEndAsync();
        var stderrTask = process.StandardError.ReadToEndAsync();
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        await process.WaitForExitAsync(timeout.Token);
 
        var result = new CommandResult(process.ExitCode, await stdoutTask + await stderrTask);
        output.WriteLine(result.Output);
 
        return result;
    }
 
    public void Dispose() => _workspace.Dispose();
 
    private sealed record CommandResult(int ExitCode, string Output);
}