File: AnalyzeCiFailureBestEffortTests.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 System.IO.Compression;
using System.Text.Json.Nodes;
using AnalyzeCiFailure;
using Xunit;
 
namespace Infrastructure.Tests;
 
/// <summary>Checks retained partial results and cumulative work budgets across failed acquisitions.</summary>
public sealed partial class AnalyzeCiFailureWorkflowTests
{
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void ConfirmedRunnerLossCannotBecomeRepositoryBreakageWithoutObservedTests(bool runnerLost)
    {
        var evidence = AnalyzeCiFailureFixtures.CreateEvidence(RunScope.Main);
        evidence = evidence with
        {
            Jobs = [evidence.Jobs[0] with { RunnerFailure = runnerLost ? WorkflowRunEvidenceCollector.RunnerLostAnnotation : null }],
        };
        var proposal = AnalyzeCiFailureFixtures.CreateInfrastructureProposal(evidence) with
        {
            Jobs = [new ProposedJobDecision { JobId = 123, Classification = JobClassification.MainRepositoryBreakage, Reason = "Claimed repository failure" }],
            Causes =
            [
                new NewCauseProposal
                {
                    Definition = new ProposedCauseDefinition { Id = "claimed-breakage", Type = CauseType.MainRepositoryBreakage, Title = "Claimed breakage", ErrorPattern = "Build failed" },
                    JobIds = [123], TestRefs = [], Reason = "Claimed repository failure",
                },
            ],
        };
        if (runnerLost)
        {
            var error = Assert.Throws<ValidationException>(() => AnalysisValidator.Validate(evidence, proposal, "microsoft/aspire"));
            Assert.Contains("confirmed runner-loss", error.Message, StringComparison.Ordinal);
            return;
        }
        Assert.Equal(AnalysisVerdict.MainRepositoryBreakage, AnalysisValidator.Validate(evidence, proposal, "microsoft/aspire").Report.Verdict);
    }
 
    [Fact]
    public async Task MalformedFileDoesNotDiscardTheOtherReportsInItsArtifact()
    {
        var root = Path.Combine(_workspace.Path, "partial-results");
        var directory = Directory.CreateDirectory(Path.Combine(root, "10")).FullName;
        await File.WriteAllTextAsync(Path.Combine(directory, "00001.trx"), FrameworkResult("Tests.First", "first", "first"));
        await File.WriteAllTextAsync(Path.Combine(directory, "00002.trx"), "malformed");
        await File.WriteAllTextAsync(Path.Combine(directory, "00003.trx"), FrameworkResult("Tests.Last", "last", "last"));
        var evidence = AnalyzeCiFailureFixtures.CreateEvidence(RunScope.PullRequest);
        var artifact = new SelectedTestArtifact(
            new WorkflowArtifact(JsonValue.Create(10), "results", JsonValue.Create(1), false, "2026-10-29T00:00:00Z"),
            new TestArtifactExpectation("Tests", "results", TestArtifactFormat.Trx, TestEvidencePolicy.Required));
        var problems = new List<string>();
 
        var failures = TestFailureReader.ConsolidateFailures(
            TestFailureReader.ReadAvailable(root, evidence.Jobs, [artifact], problems.Add, reportNonTestFailure: (_, _) => { }));
        Assert.Equal(["Tests.First", "Tests.Last"], failures.Select(failure => failure.Name));
        Assert.Contains("00002.trx", Assert.Single(problems), StringComparison.Ordinal);
        var partial = evidence with
        {
            TestEvidence = evidence.TestEvidence with { State = EvidenceState.Unavailable, Failures = failures },
        };
        var reloaded = TransientJson.Read<EvidenceBundle>(TransientJson.Write(partial));
        AnalysisValidator.ValidateBundle(reloaded, "microsoft/aspire");
        var error = Assert.Throws<ValidationException>(() => AnalysisValidator.Validate(
            reloaded, AnalyzeCiFailureFixtures.CreateInfrastructureProposal(reloaded), "microsoft/aspire"));
        Assert.Contains("publication and reruns are not authorized", error.Message, StringComparison.Ordinal);
    }
 
    [Fact]
    public async Task FailedExtractionsConsumeTheSharedExpansionBudget()
    {
        var archivePath = Path.Combine(_workspace.Path, "failed-expansion.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "results.trx", "12345678");
            await WriteZipEntryAsync(archive, "../unsafe", "unsafe");
        }
        long remaining = 10;
 
        Assert.Throws<ValidationException>(() => TestResultArchiveExtractor.ExtractTracked(
            archivePath,
            Path.Combine(_workspace.Path, "first"),
            remaining,
            expectedBytes: null,
            format: "trx",
            count => remaining -= count));
        Assert.Equal(2, remaining);
        Assert.Throws<ValidationException>(() => TestResultArchiveExtractor.ExtractTracked(
            archivePath,
            Path.Combine(_workspace.Path, "second"),
            remaining,
            expectedBytes: null,
            format: "trx",
            count => remaining -= count));
        Assert.Equal(-1, remaining);
    }
}