File: AnalyzeCiFailureBootstrapTests.cs
Web Access
Project: src\tests\AnalyzeCiFailure.Tests\AnalyzeCiFailure.Tests.csproj (AnalyzeCiFailure.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.TestUtilities;
using Xunit;
 
namespace Infrastructure.Tests;
 
/// <summary>Builds and runs the analyzer from a real sparse checkout isolated from hostile parent imports.</summary>
public sealed partial class AnalyzeCiFailureWorkflowTests
{
    [Fact]
    [RequiresTools(["git", "dotnet"])]
    public async Task AnalyzerBuildsFromRealSparseCheckoutWithoutHostileParentImports()
    {
        var seed = Directory.CreateDirectory(Path.Combine(_workspace.Path, "seed")).FullName;
        var analyzerDirectory = Path.Combine(RepoRoot.Path, "tools", "AnalyzeCiFailure");
        foreach (var relative in new[] { "global.json", "NuGet.config", "eng/test-retry-patterns.json" }
            .Concat(Directory.GetFiles(Path.Combine(RepoRoot.Path, "tools", "Aspire.TestTools"), "*.cs")
                .Select(file => Path.GetRelativePath(RepoRoot.Path, file)))
            .Concat(Directory.GetFiles(analyzerDirectory, "*", SearchOption.AllDirectories)
                .Where(file => Path.GetRelativePath(analyzerDirectory, file).Split(Path.DirectorySeparatorChar)[0] is not ("bin" or "obj"))
                .Select(file => Path.GetRelativePath(RepoRoot.Path, file))))
        {
            var target = Path.Combine(seed, relative);
            Directory.CreateDirectory(Path.GetDirectoryName(target)!);
            File.Copy(Path.Combine(RepoRoot.Path, relative), target);
        }
        foreach (var args in new string[][]
        {
            ["--no-pager", "-C", seed, "init"],
            ["--no-pager", "-C", seed, "config", "user.name", "Test"],
            ["--no-pager", "-C", seed, "config", "user.email", "test@example.invalid"],
            ["--no-pager", "-C", seed, "add", "--", "global.json", "NuGet.config", "tools/AnalyzeCiFailure", "tools/Aspire.TestTools", "eng/test-retry-patterns.json"],
            ["--no-pager", "-C", seed, "commit", "-m", "Seed sparse bootstrap fixture"],
        })
        {
            var setup = await RunProcessAsync("git", args);
            Assert.True(setup.ExitCode == 0, setup.Output);
        }
        var hostile = Directory.CreateDirectory(Path.Combine(_workspace.Path, "hostile")).FullName;
        await File.WriteAllTextAsync(
            Path.Combine(hostile, "Directory.Build.props"),
            """<Project><Import Project="missing-hostile-parent.props" /></Project>""");
        await File.WriteAllTextAsync(
            Path.Combine(hostile, "Directory.Build.targets"),
            """<Project><Target Name="Poison" BeforeTargets="Build"><Error Text="Hostile parent targets were imported" /></Target></Project>""");
        await File.WriteAllTextAsync(
            Path.Combine(hostile, "Directory.Packages.props"),
            """<Project><Import Project="missing-hostile-packages.props" /></Project>""");
        var checkout = Path.Combine(hostile, "checkout");
        foreach (var args in new string[][]
        {
            ["--no-pager", "clone", "--no-checkout", seed, checkout],
            ["--no-pager", "-C", checkout, "sparse-checkout", "init", "--no-cone"],
            ["--no-pager", "-C", checkout, "sparse-checkout", "set", "--no-cone",
                "/global.json", "/NuGet.config", "/tools/AnalyzeCiFailure/", "/tools/Aspire.TestTools/", "/eng/test-retry-patterns.json"],
            ["--no-pager", "-C", checkout, "checkout"],
        })
        {
            var setup = await RunProcessAsync("git", args);
            Assert.True(setup.ExitCode == 0, setup.Output);
        }
        var project = Path.Combine(checkout, "tools", "AnalyzeCiFailure", "AnalyzeCiFailure.csproj");
        var dotnet = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet";
        var restore = await RunProcessAsync(dotnet, ["restore", project, "--verbosity", "quiet"], workingDirectory: checkout);
        Assert.True(restore.ExitCode == 0, restore.Output);
        var build = await RunProcessAsync(dotnet, ["build", project, "--no-restore", "--configuration", "Release", "--verbosity", "quiet"], workingDirectory: checkout);
        Assert.True(build.ExitCode == 0, build.Output);
        var run = await RunProcessAsync(
            dotnet,
            [Path.Combine(checkout, "tools", "AnalyzeCiFailure", "bin", "Release", "net11.0", "AnalyzeCiFailure.dll")],
            environment: new Dictionary<string, string> { ["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "1" },
            workingDirectory: checkout);
        Assert.Equal(2, run.ExitCode);
        Assert.Contains("Required command was not provided.", run.Output, StringComparison.Ordinal);
        Assert.Contains("Usage:", run.Output, StringComparison.Ordinal);
 
        var results = await WritePreparationFixtureAsync();
        await File.WriteAllTextAsync(Path.Combine(results, "00001.trx"), "<TestRun><Results/></TestRun>");
        var parsed = await RunPreparationAsync();
        Assert.True(parsed.ExitCode == 0, parsed.Output);
        var evidenceDirectory = EvidenceDirectory;
        foreach (var source in Directory.GetFiles(evidenceDirectory, "*", SearchOption.AllDirectories))
        {
            var target = Path.Combine(checkout, "ci-failure-analysis/evidence", Path.GetRelativePath(evidenceDirectory, source));
            Directory.CreateDirectory(Path.GetDirectoryName(target)!);
            File.Copy(source, target);
        }
        await File.WriteAllTextAsync(
            Path.Combine(checkout, "ci-failure-analysis/evidence", "analysis-summary.md"),
            "Copied input summary must not satisfy the sparse replay assertion.");
        var preparation = await RunProcessAsync(
            dotnet,
            [Path.Combine(checkout, "tools", "AnalyzeCiFailure", "bin", "Release", "net11.0", "AnalyzeCiFailure.dll"),
                "replay", "--evidence-dir", Path.Combine(checkout, "ci-failure-analysis/evidence"), "--working-dir", checkout],
            workingDirectory: checkout);
        Assert.True(preparation.ExitCode == 0, preparation.Output);
        var replayDirectory = ReadNamedPath(preparation.Output, "replay_dir");
        Assert.Contains(
            "## Prior Causes (from memory branch)",
            await File.ReadAllTextAsync(Path.Combine(replayDirectory, "analysis-summary.md")),
            StringComparison.Ordinal);
        Assert.Equal(
            "Copied input summary must not satisfy the sparse replay assertion.",
            await File.ReadAllTextAsync(Path.Combine(checkout, "ci-failure-analysis/evidence", "analysis-summary.md")));
    }
}