File: Pipelines\ExtensionE2eWorkflowTests.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 Xunit;
using YamlDotNet.RepresentationModel;
 
namespace Infrastructure.Tests.Pipelines;
 
/// <summary>
/// Guards the advisory-failure contract in <c>.github/workflows/extension-e2e-tests.yml</c>.
///
/// Every shard still runs and produces diagnostics. Only issue-tracked rows may treat completed
/// VS Code test failures as advisory; setup, harness, and cleanup failures remain blocking.
/// </summary>
public sealed class ExtensionE2eWorkflowTests
{
    private const string CallerWorkflowRelativePath = ".github/workflows/tests.yml";
    [Fact]
    public void AdvisoryShardRowsRemainIssueTrackedWithoutWeakeningRunnerStep()
    {
        var job = LoadExtensionE2eJob();
 
        var rows = MatrixIncludeRows(job).ToList();
        Assert.NotEmpty(rows);
        Assert.All(rows, row =>
        {
            Assert.False(row.Children.ContainsKey(new YamlScalarNode("allowFailure")));
            Assert.False(row.Children.ContainsKey(new YamlScalarNode("disabledIssue")));
        });
        Assert.Contains(rows, row => row.Children.ContainsKey(new YamlScalarNode("advisoryIssue")));
        Assert.Contains(rows, row => !row.Children.ContainsKey(new YamlScalarNode("advisoryIssue")));
 
        var steps = ((YamlSequenceNode)job.Children[new YamlScalarNode("steps")]).Cast<YamlMappingNode>().ToList();
        var runSuiteStep = Assert.Single(steps, step => Scalar(step, "name") == "Run extension E2E tests");
        Assert.False(runSuiteStep.Children.ContainsKey(new YamlScalarNode("if")));
        Assert.False(runSuiteStep.Children.ContainsKey(new YamlScalarNode("continue-on-error")));
 
        var environment = (YamlMappingNode)runSuiteStep.Children[new YamlScalarNode("env")];
        Assert.False(environment.Children.ContainsKey(new YamlScalarNode("ASPIRE_EXTENSION_E2E_ALLOW_TEST_FAILURE")));
        Assert.Equal("${{ matrix.advisoryIssue }}", Scalar(environment, "ASPIRE_EXTENSION_E2E_ADVISORY_ISSUE"));
 
        var prepareCliStep = Assert.Single(steps, step => Scalar(step, "name") == "Prepare Aspire CLI and package hive");
        var prepareCliScript = Scalar(prepareCliStep, "run") ?? string.Empty;
        Assert.Contains(
            "\"ASPIRE_DCP_PATH=$($dcp.Directory.FullName)\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append",
            prepareCliScript,
            StringComparison.Ordinal);
    }
 
    [Fact]
    public void SelectedExtensionE2eWorkflowMustRun()
    {
        var yaml = new YamlStream();
        using var reader = new StringReader(File.ReadAllText(Path.Combine(RepoRoot.Path, CallerWorkflowRelativePath)));
        yaml.Load(reader);
 
        var root = (YamlMappingNode)yaml.Documents[0].RootNode;
        var jobs = (YamlMappingNode)root.Children[new YamlScalarNode("jobs")];
        var extensionE2eJob = (YamlMappingNode)jobs.Children[new YamlScalarNode("extension_e2e_tests")];
 
        // The condition is a folded block, so compare on collapsed whitespace rather than the layout.
        var e2eCondition = string.Join(' ', (Scalar(extensionE2eJob, "if") ?? string.Empty).Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
 
        // Selection still decides whether the shards run at all.
        Assert.Contains("needs.setup_for_tests.outputs.run_extension_e2e == 'true'", e2eCondition, StringComparison.Ordinal);
 
        // !cancelled() rather than success(): extension_tests_win is in `needs` only because it
        // uploads the VSIX the shards install, and its packaging steps carry the same condition, so a
        // unit-test failure must not take the E2E signal with it. Assert the job's result is not
        // consulted at all -- reintroducing it is the regression this pins.
        Assert.StartsWith("${{ !cancelled() &&", e2eCondition, StringComparison.Ordinal);
        Assert.DoesNotContain("needs.extension_tests_win.result", e2eCondition, StringComparison.Ordinal);
 
        // The artifact producers stay required by result: without them every shard fails on a
        // download, which reports nothing useful about the extension.
        foreach (var producer in new[] { "build_packages", "build_cli_archive_linux", "build_cli_archive_windows", "extension_bootstrap_linux" })
        {
            Assert.Contains($"needs.{producer}.result == 'success'", e2eCondition, StringComparison.Ordinal);
        }
 
        // The VSIX the shards install is published even when the unit tests fail; without this the
        // decoupling above buys nothing because the artifact would never exist.
        var extensionUnitJob = (YamlMappingNode)jobs.Children[new YamlScalarNode("extension_tests_win")];
        var unitSteps = ((YamlSequenceNode)extensionUnitJob.Children[new YamlScalarNode("steps")]).Cast<YamlMappingNode>().ToList();
        var uploadStep = Assert.Single(unitSteps, step => Scalar(step, "name") == "Upload VSIX");
        Assert.Contains("!cancelled()", Scalar(uploadStep, "if") ?? string.Empty, StringComparison.Ordinal);
        var packageStep = Assert.Single(unitSteps, step => Scalar(step, "name") == "Package VSIX");
        Assert.Contains("!cancelled()", Scalar(packageStep, "if") ?? string.Empty, StringComparison.Ordinal);
 
        var resultsJob = (YamlMappingNode)jobs.Children[new YamlScalarNode("results")];
        var steps = (YamlSequenceNode)resultsJob.Children[new YamlScalarNode("steps")];
        var failureStep = Assert.Single(steps.Cast<YamlMappingNode>(), step => Scalar(step, "name") == "Fail if any dependency failed");
        var condition = Scalar(failureStep, "if");
        const string unexpectedSkipCheck = "needs.extension_e2e_tests.result == 'skipped'";
        Assert.Equal(2, condition?.Split(unexpectedSkipCheck, StringSplitOptions.None).Length - 1);
    }
 
    private static YamlMappingNode LoadExtensionE2eJob() => ExtensionE2eWorkflow.Job();
 
    private static IEnumerable<YamlMappingNode> MatrixIncludeRows(YamlMappingNode job)
    {
        var strategy = (YamlMappingNode)job.Children[new YamlScalarNode("strategy")];
        var matrix = (YamlMappingNode)strategy.Children[new YamlScalarNode("matrix")];
 
        return ((YamlSequenceNode)matrix.Children[new YamlScalarNode("include")]).Cast<YamlMappingNode>();
    }
 
    private static string? Scalar(YamlMappingNode node, string key)
        => node.Children.TryGetValue(new YamlScalarNode(key), out var value) && value is YamlScalarNode scalar
            ? scalar.Value
            : null;
}