diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/AzureDevOpsServiceTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/AzureDevOpsServiceTests.cs index 58f72c3f4c0..2918742df1f 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/AzureDevOpsServiceTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/AzureDevOpsServiceTests.cs @@ -8,6 +8,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using AwesomeAssertions; using Microsoft.DotNet.Helix.JobMonitor; using Microsoft.DotNet.Helix.Sdk.Tests.Fakes; using Microsoft.Extensions.Logging.Abstractions; @@ -30,20 +31,20 @@ public async Task CreateTestRunAsync_EmbedsHelixJobNameInRunName() int testRunId = await service.CreateTestRunAsync("Test Run", "helix-job-1", CancellationToken.None); - Assert.Equal(123, testRunId); - HttpRequestMessage request = Assert.Single(handler.Requests); - Assert.Equal(HttpMethod.Post, request.Method); - Assert.Equal("https://dev.azure.com/dnceng-public/public/_apis/test/runs?api-version=7.1", request.RequestUri.ToString()); + testRunId.Should().Be(123); + HttpRequestMessage request = handler.Requests.Should().ContainSingle().Subject; + request.Method.Should().Be(HttpMethod.Post); + request.RequestUri.ToString().Should().Be("https://dev.azure.com/dnceng-public/public/_apis/test/runs?api-version=7.1"); - JObject body = JObject.Parse(Assert.Single(handler.Bodies)); - Assert.Equal("Test Run [HelixJob:helix-job-1]", body.Value("name")); - Assert.Equal("InProgress", body.Value("state")); - Assert.Equal("1403994", body["build"]?.Value("id")); - Assert.Null(body.Value("comment")); + JObject body = JObject.Parse(handler.Bodies.Should().ContainSingle().Subject); + body.Value("name").Should().Be("Test Run [HelixJob:helix-job-1]"); + body.Value("state").Should().Be("InProgress"); + body["build"]?.Value("id").Should().Be("1403994"); + body.Value("comment").Should().BeNull(); // We deliberately do not send tags: Azure DevOps silently drops them on // POST /test/runs (verified empirically), so the helix job name is round-tripped // via the run name marker. - Assert.Null(body["tags"]); + body["tags"].Should().BeNull(); } [Fact] @@ -66,11 +67,11 @@ public async Task GetProcessedHelixJobNamesAsync_RecoversFromTestRunNameMarker() IReadOnlySet processed = await service.GetProcessedHelixJobNamesAsync(CancellationToken.None); - Assert.Equal(["helix-linux", "helix-windows"], processed.OrderBy(static name => name)); + processed.Should().BeEquivalentTo(["helix-linux", "helix-windows"]); // Single list call, no per-run detail fetches: the marker is in the run "name" // which is always part of the list response. - HttpRequestMessage request = Assert.Single(handler.Requests); - Assert.EndsWith("/_apis/test/runs?buildUri=vstfs%3A%2F%2F%2FBuild%2FBuild%2F1403994&$top=1000&api-version=7.1", request.RequestUri.ToString()); + HttpRequestMessage request = handler.Requests.Should().ContainSingle().Subject; + request.RequestUri.ToString().Should().EndWith("/_apis/test/runs?buildUri=vstfs%3A%2F%2F%2FBuild%2FBuild%2F1403994&$top=1000&api-version=7.1"); } [Fact] @@ -85,12 +86,12 @@ public async Task CompleteTestRunAsync_SendsCompletedState() await service.CompleteTestRunAsync(123, CancellationToken.None); - HttpRequestMessage request = Assert.Single(handler.Requests); - Assert.Equal(new HttpMethod("PATCH"), request.Method); - Assert.Equal("https://dev.azure.com/dnceng-public/public/_apis/test/runs/123?api-version=7.1", request.RequestUri.ToString()); + HttpRequestMessage request = handler.Requests.Should().ContainSingle().Subject; + request.Method.Should().Be(new HttpMethod("PATCH")); + request.RequestUri.ToString().Should().Be("https://dev.azure.com/dnceng-public/public/_apis/test/runs/123?api-version=7.1"); - JObject body = JObject.Parse(Assert.Single(handler.Bodies)); - Assert.Equal("Completed", body.Value("state")); + JObject body = JObject.Parse(handler.Bodies.Should().ContainSingle().Subject); + body.Value("state").Should().Be("Completed"); } [Fact] @@ -110,9 +111,9 @@ public async Task TagsArePostedButNeverObservableViaGet_DocumentsAzdoBehavior() IReadOnlySet processed = await service.GetProcessedHelixJobNamesAsync(CancellationToken.None); // Helix job name round-trips via the run "name" suffix, NOT via tags. - Assert.Contains("helix-job-1", processed); - Assert.Equal("Test Run [HelixJob:helix-job-1]", handler.Runs[runId].Name); - Assert.Equal("Completed", handler.Runs[runId].State); + processed.Should().Contain("helix-job-1"); + handler.Runs[runId].Name.Should().Be("Test Run [HelixJob:helix-job-1]"); + handler.Runs[runId].State.Should().Be("Completed"); } private static JobMonitorOptions CreateOptions() diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index 9852ac0c68b..e9235264bb8 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using AwesomeAssertions; using Microsoft.DotNet.Helix.Client.Models; using Microsoft.DotNet.Helix.JobMonitor; using Microsoft.DotNet.Helix.JobMonitor.Models; @@ -54,10 +55,10 @@ public async Task SinglePipelineJobSucceeds_NoHelixJobs_ExitZero() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(3, azdo.TimelineCallCount); - Assert.Empty(azdo.CreatedTestRuns); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.TimelineCallCount.Should().Be(3); + azdo.CreatedTestRuns.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -104,10 +105,10 @@ public async Task TwoPipelineJobs_OnePassesOneFails_NoHelixJobs_ExitOne() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Equal(4, azdo.TimelineCallCount); - Assert.Empty(azdo.CreatedTestRuns); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(1); + azdo.TimelineCallCount.Should().Be(4); + azdo.CreatedTestRuns.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -146,10 +147,10 @@ public async Task TwoPipelineJobs_AllFailed_NoHelixJobs_ExitOne() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Equal(3, azdo.TimelineCallCount); - Assert.Empty(azdo.CreatedTestRuns); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(1); + azdo.TimelineCallCount.Should().Be(3); + azdo.CreatedTestRuns.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -188,10 +189,10 @@ public async Task TwoPipelineJobs_AllPassed_NoHelixJobs_ExitZero() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(3, azdo.TimelineCallCount); - Assert.Empty(azdo.CreatedTestRuns); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.TimelineCallCount.Should().Be(3); + azdo.CreatedTestRuns.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -243,8 +244,8 @@ public async Task RetryAfterFailure_RetriedJobPasses_ExitZero() var runner1 = CreateRunner(azdo1, helix1); int exitCode1 = await runner1.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode1); - Assert.Equal(4, azdo1.TimelineCallCount); + exitCode1.Should().Be(1); + azdo1.TimelineCallCount.Should().Be(4); // --- Retry (attempt 2): monitor and Build Windows re-run --- // AzDO replaces the retried jobs' records with attempt=2. @@ -280,10 +281,10 @@ public async Task RetryAfterFailure_RetriedJobPasses_ExitZero() var runner2 = CreateRunner(azdo2, helix2); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode2); - Assert.Equal(3, azdo2.TimelineCallCount); - Assert.Empty(azdo2.CreatedTestRuns); - Assert.Empty(azdo2.UploadedJobNames); + exitCode2.Should().Be(0); + azdo2.TimelineCallCount.Should().Be(3); + azdo2.CreatedTestRuns.Should().BeEmpty(); + azdo2.UploadedJobNames.Should().BeEmpty(); } /// @@ -353,17 +354,17 @@ public async Task BuildJobSubmitsHelixWork_WorkItemsPassed_ResultsUploaded_ExitZ int exitCode = await runner.RunAsync(CancellationToken.None); // Monitor should exit successfully - Assert.Equal(0, exitCode); + exitCode.Should().Be(0); // 6 poll iterations (5 delays before exit on 6th) - Assert.Equal(6, azdo.TimelineCallCount); + azdo.TimelineCallCount.Should().Be(6); // One test run created and completed for the Helix job - Assert.Single(azdo.CreatedTestRuns); - Assert.Single(azdo.CompletedTestRunIds); + azdo.CreatedTestRuns.Should().ContainSingle(); + azdo.CompletedTestRunIds.Should().ContainSingle(); // Test results uploaded for the Helix job - Assert.Equal(["helix-linux-tests"], azdo.UploadedJobNames); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-tests"]); } [Fact] @@ -396,19 +397,19 @@ public async Task CompletedHelixJob_QueuesTestResultUploadWithoutBlockingNextPol async (_, _) => { Task completed = await Task.WhenAny(azdo.UploadStarted.Task, Task.Delay(TimeSpan.FromSeconds(5))); - Assert.Same(azdo.UploadStarted.Task, completed); + completed.Should().BeSameAs(azdo.UploadStarted.Task); delayedBeforeUploadCompleted = !azdo.UploadCompleted.Task.IsCompleted; uploadRelease.SetResult(); }); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.True(delayedBeforeUploadCompleted); - Assert.Equal(2, azdo.TimelineCallCount); - Assert.Equal(["helix-linux"], azdo.UploadedJobNames); - Assert.Single(azdo.CompletedTestRunIds); - Assert.Contains(logger.Messages, message => + exitCode.Should().Be(0); + delayedBeforeUploadCompleted.Should().BeTrue(); + azdo.TimelineCallCount.Should().Be(2); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); + azdo.CompletedTestRunIds.Should().ContainSingle(); + logger.Messages.Should().Contain(message => message.Contains("2 test results for job 'helix-linux' processed.", StringComparison.Ordinal)); } @@ -447,12 +448,12 @@ public async Task PassedHelixWork_UploadFailsOnce_RetriesAndProcessesResults() }); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(2, azdo.UploadTestResultsCallCount); - Assert.Equal(1, delayCount); - Assert.Single(azdo.CreatedTestRuns); - Assert.Single(azdo.CompletedTestRunIds); - Assert.Equal(["helix-linux"], azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.UploadTestResultsCallCount.Should().Be(2); + delayCount.Should().Be(1); + azdo.CreatedTestRuns.Should().ContainSingle(); + azdo.CompletedTestRunIds.Should().ContainSingle(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -490,11 +491,11 @@ public async Task MultipleWorkItems_FinishAtDifferentTimes_AllPass_ExitZero() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(4, azdo.TimelineCallCount); - Assert.Single(azdo.CreatedTestRuns); - Assert.Single(azdo.CompletedTestRunIds); - Assert.Equal(["helix-linux"], azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.TimelineCallCount.Should().Be(4); + azdo.CreatedTestRuns.Should().ContainSingle(); + azdo.CompletedTestRunIds.Should().ContainSingle(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -562,13 +563,13 @@ public async Task TwoJobsSubmitHelixWork_MixedPassFail_ExitOne() int exitCode = await runner.RunAsync(CancellationToken.None); // Exit 1 because helix-linux had a failed work item - Assert.Equal(1, exitCode); - Assert.Equal(5, azdo.TimelineCallCount); + exitCode.Should().Be(1); + azdo.TimelineCallCount.Should().Be(5); // Both Helix jobs had results uploaded - Assert.Equal(2, azdo.CreatedTestRuns.Count); - Assert.Equal(2, azdo.CompletedTestRunIds.Count); - Assert.Contains("helix-linux", azdo.UploadedJobNames); - Assert.Contains("helix-windows", azdo.UploadedJobNames); + azdo.CreatedTestRuns.Should().HaveCount(2); + azdo.CompletedTestRunIds.Should().HaveCount(2); + azdo.UploadedJobNames.Should().Contain("helix-linux"); + azdo.UploadedJobNames.Should().Contain("helix-windows"); } /// @@ -607,7 +608,7 @@ public async Task TwoAzDOJobs_SameWorkItemName_OneFails_OtherPasses_FailurePropa // The Linux work-item failure must not be overwritten by the same-named work item // that passed on Windows. Final summary must report 1 failed work item and the // monitor must exit non-zero. - Assert.Equal(1, exitCode); + exitCode.Should().Be(1); } /// @@ -648,7 +649,7 @@ public async Task OneSubmitter_FansOutToMultipleQueues_SameWorkItemName_FailureN var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); + exitCode.Should().Be(1); } [Fact] @@ -704,11 +705,10 @@ public async Task StageRerun_UploadsNewHelixWorkItemsWithoutReuploadingPreviousW var runner2 = CreateRunner(azdo, helix2, stageName: "Test"); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode1); - Assert.Equal(0, exitCode2); - Assert.Equal( - new[] { "old-helix-linux", "old-helix-windows", "new-helix-linux", "new-helix-windows" }.Order(), - azdo.UploadedJobNames.Order()); + exitCode1.Should().Be(0); + exitCode2.Should().Be(0); + azdo.UploadedJobNames.Should().BeEquivalentTo( + ["old-helix-linux", "old-helix-windows", "new-helix-linux", "new-helix-windows"]); var uploadedWorkItems = azdo.UploadedResultsByRunId .Values @@ -716,13 +716,13 @@ public async Task StageRerun_UploadsNewHelixWorkItemsWithoutReuploadingPreviousW .Select(result => $"{result.JobName}/{result.WorkItemName}") .ToList(); - Assert.Equal(uploadedWorkItems.Count, uploadedWorkItems.Distinct(StringComparer.OrdinalIgnoreCase).Count()); - Assert.Contains("old-helix-linux/common-work-item", uploadedWorkItems); - Assert.Contains("old-helix-windows/common-work-item", uploadedWorkItems); - Assert.Contains("new-helix-linux/common-work-item", uploadedWorkItems); - Assert.Contains("new-helix-linux/linux-only-work-item", uploadedWorkItems); - Assert.Contains("new-helix-windows/common-work-item", uploadedWorkItems); - Assert.Contains("new-helix-windows/windows-only-work-item", uploadedWorkItems); + uploadedWorkItems.Should().OnlyHaveUniqueItems(); + uploadedWorkItems.Should().Contain("old-helix-linux/common-work-item"); + uploadedWorkItems.Should().Contain("old-helix-windows/common-work-item"); + uploadedWorkItems.Should().Contain("new-helix-linux/common-work-item"); + uploadedWorkItems.Should().Contain("new-helix-linux/linux-only-work-item"); + uploadedWorkItems.Should().Contain("new-helix-windows/common-work-item"); + uploadedWorkItems.Should().Contain("new-helix-windows/windows-only-work-item"); } /// @@ -760,9 +760,9 @@ public async Task StageScopedMonitor_IgnoresJobsOutsideStage_ExitZero() // Monitor only watches Test stage — Test Linux passed, no Helix → exit 0 // Build Windows being in progress doesn't block the monitor. - Assert.Equal(0, exitCode); - Assert.Equal(2, azdo.TimelineCallCount); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.TimelineCallCount.Should().Be(2); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -792,10 +792,10 @@ public async Task StageScopedMonitor_IgnoresHelixJobsFromOtherStage_ExitZero() int exitCode = await runner.RunAsync(CancellationToken.None); // Test stage is done, no Helix jobs in Test stage → exit 0 - Assert.Equal(0, exitCode); - Assert.Equal(1, azdo.TimelineCallCount); - Assert.Empty(azdo.UploadedJobNames); - Assert.Empty(azdo.CreatedTestRuns); + exitCode.Should().Be(0); + azdo.TimelineCallCount.Should().Be(1); + azdo.UploadedJobNames.Should().BeEmpty(); + azdo.CreatedTestRuns.Should().BeEmpty(); } /// @@ -826,10 +826,10 @@ public async Task StageScopedMonitor_DoesNotResubmitFailedHelixJobsOutsideStage_ var runner = CreateRunner(azdo, helix, stageName: "Test"); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Empty(helix.Resubmissions); - Assert.Empty(azdo.UploadedJobNames); - Assert.Empty(azdo.CreatedTestRuns); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEmpty(); + azdo.CreatedTestRuns.Should().BeEmpty(); } /// @@ -856,16 +856,16 @@ public async Task StageScopedMonitors_ShutDownWhenTheirOwnStageWorkCompletes() int testExitCode = await testRunner.RunAsync(CancellationToken.None); int buildExitCode = await buildRunner.RunAsync(CancellationToken.None); - Assert.Equal(0, testExitCode); - Assert.Equal(0, buildExitCode); + testExitCode.Should().Be(0); + buildExitCode.Should().Be(0); // Test stage finishes in frame 2 while Build stage and its Helix job are still running. - Assert.Equal(2, testAzdo.TimelineCallCount); - Assert.Equal(["helix-test-linux"], testAzdo.UploadedJobNames); + testAzdo.TimelineCallCount.Should().Be(2); + testAzdo.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux"]); // Build stage pipeline work finishes in frame 3, but its Helix job finishes in frame 4. - Assert.Equal(4, buildAzdo.TimelineCallCount); - Assert.Equal(["helix-build-windows"], buildAzdo.UploadedJobNames); + buildAzdo.TimelineCallCount.Should().Be(4); + buildAzdo.UploadedJobNames.Should().BeEquivalentTo(["helix-build-windows"]); static void AddStageTimelineFrames(FakeAzureDevOpsService azdo) { @@ -982,9 +982,9 @@ public async Task StageScopedMonitor_OnStageRetry_OnlyRetriesFailedHelixWorkFrom var runner1 = CreateRunner(azdo1, helix1, stageName: "Test"); int exitCode1 = await runner1.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode1); - Assert.Empty(helix1.Resubmissions); - Assert.Equal(["helix-test-linux"], azdo1.UploadedJobNames); + exitCode1.Should().Be(1); + helix1.Resubmissions.Should().BeEmpty(); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-test-linux"); @@ -1023,12 +1023,12 @@ public async Task StageScopedMonitor_OnStageRetry_OnlyRetriesFailedHelixWorkFrom var runner2 = CreateRunner(azdo2, helix2, stageName: "Test"); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode2); - Assert.Single(helix2.Resubmissions); - Assert.Equal("helix-test-linux", helix2.Resubmissions[0].OriginalJob); - Assert.Equal(["test-fail"], helix2.Resubmissions[0].FailedItems); - Assert.DoesNotContain(helix2.Resubmissions, r => r.OriginalJob == "helix-build-windows"); - Assert.Equal(["helix-test-linux-resub"], azdo2.UploadedJobNames); + exitCode2.Should().Be(0); + helix2.Resubmissions.Should().ContainSingle(); + helix2.Resubmissions[0].OriginalJob.Should().Be("helix-test-linux"); + helix2.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["test-fail"]); + helix2.Resubmissions.Should().NotContain(r => r.OriginalJob == "helix-build-windows"); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux-resub"]); static void AddSingleMonitorStageTimeline(FakeAzureDevOpsService azdo, int attempt) { @@ -1094,12 +1094,12 @@ public async Task StageScopedMonitor_OnRetry_IgnoresDefaultRefNameMonitorJobUnde int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Equal(1, delayCount); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-linux", helix.Resubmissions[0].OriginalJob); - Assert.Equal(["linux-fail"], helix.Resubmissions[0].FailedItems); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(1); + delayCount.Should().Be(1); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["linux-fail"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); static void AddRetriedStageTimeline(FakeAzureDevOpsService azdo) { @@ -1187,8 +1187,8 @@ public async Task MonitorTimesOut_Relaunched_UploadsRemainingResults() int exitCode1 = await runner1.RunAsync(cts.Token); // Timed out → exit 1. helix-linux was uploaded, helix-windows was not. - Assert.Equal(1, exitCode1); - Assert.Equal(["helix-linux"], azdo1.UploadedJobNames); + exitCode1.Should().Be(1); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); // --- Second run: monitor relaunched, helix-linux already processed --- var azdo2 = new FakeAzureDevOpsService(); @@ -1213,9 +1213,9 @@ public async Task MonitorTimesOut_Relaunched_UploadsRemainingResults() int exitCode2 = await runner2.RunAsync(CancellationToken.None); // helix-linux skipped (already processed), helix-windows uploaded → exit 0 - Assert.Equal(0, exitCode2); - Assert.Equal(["helix-windows"], azdo2.UploadedJobNames); - Assert.Single(azdo2.CreatedTestRuns); + exitCode2.Should().Be(0); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-windows"]); + azdo2.CreatedTestRuns.Should().ContainSingle(); } [Fact] @@ -1248,24 +1248,22 @@ public async Task MonitorTimesOut_CancelsLatestInFlightHelixJobs() async (_, _) => { Task completed = await Task.WhenAny(azdo.UploadCompleted.Task, Task.Delay(TimeSpan.FromSeconds(5))); - Assert.Same(azdo.UploadCompleted.Task, completed); + completed.Should().BeSameAs(azdo.UploadCompleted.Task); cts.Cancel(); }); int exitCode = await runner.RunAsync(cts.Token); - Assert.Equal(1, exitCode); - Assert.Equal(["helix-finished"], azdo.UploadedJobNames); - Assert.Equal( - ["helix-new-attempt", "helix-running", "helix-waiting"], - helix.CanceledJobs.OrderBy(jobName => jobName, StringComparer.OrdinalIgnoreCase).ToArray()); - Assert.Contains(logger.Messages, message => + exitCode.Should().Be(1); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-finished"]); + helix.CanceledJobs.Should().BeEquivalentTo(["helix-new-attempt", "helix-running", "helix-waiting"]); + logger.Messages.Should().Contain(message => message.Contains("Helix job(s) were unfinished or unprocessed", StringComparison.Ordinal) && message.Contains("helix-new-attempt", StringComparison.Ordinal) && message.Contains("status=running", StringComparison.Ordinal) && message.Contains("helix-waiting", StringComparison.Ordinal) && message.Contains("status=waiting", StringComparison.Ordinal)); - Assert.Contains(logger.Messages, message => + logger.Messages.Should().Contain(message => message.Contains("non-monitor Azure DevOps pipeline job(s) were still in progress or queued", StringComparison.Ordinal) && message.Contains("Test Linux [state=inProgress, result=none]", StringComparison.Ordinal)); } @@ -1318,24 +1316,24 @@ public async Task MonitorTimesOut_DoesNotReportOrCancelJobsThatFinishedAfterFirs // cancelling, so the monitor has had a chance to record its terminal // state. Task completed = await Task.WhenAny(azdo.UploadCompleted.Task, Task.Delay(TimeSpan.FromSeconds(5))); - Assert.Same(azdo.UploadCompleted.Task, completed); + completed.Should().BeSameAs(azdo.UploadCompleted.Task); cts.Cancel(); } }); int exitCode = await runner.RunAsync(cts.Token); - Assert.Equal(1, exitCode); + exitCode.Should().Be(1); // helix-good must not appear in the timeout's "had not finished" list because its // cached snapshot was overwritten with the latest (finished) state. - string timeoutMessage = Assert.Single(logger.Messages, m => - m.Contains("Helix Job Monitor timed out", StringComparison.Ordinal)); - Assert.Contains("helix-stuck", timeoutMessage, StringComparison.Ordinal); - Assert.DoesNotContain("helix-good", timeoutMessage, StringComparison.Ordinal); + string timeoutMessage = logger.Messages.Should().ContainSingle(m => + m.Contains("Helix Job Monitor timed out", StringComparison.Ordinal)).Subject; + timeoutMessage.Should().Contain("helix-stuck"); + timeoutMessage.Should().NotContain("helix-good"); // And the best-effort cancel pass must only target the still-in-flight job. - Assert.Equal(["helix-stuck"], helix.CanceledJobs.OrderBy(j => j, StringComparer.OrdinalIgnoreCase).ToArray()); + helix.CanceledJobs.Should().BeEquivalentTo(["helix-stuck"]); } /// @@ -1392,8 +1390,8 @@ public async Task MonitorTimesOut_PartialProgress_Relaunched_CompletesSuccessful }); int exitCode1 = await runner1.RunAsync(cts.Token); - Assert.Equal(1, exitCode1); - Assert.Equal(["helix-linux"], azdo1.UploadedJobNames); + exitCode1.Should().Be(1); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); // --- Second run: monitor relaunched --- // Test Windows has now completed and submitted helix-windows. @@ -1432,11 +1430,11 @@ public async Task MonitorTimesOut_PartialProgress_Relaunched_CompletesSuccessful var runner2 = CreateRunner(azdo2, helix2); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode2); - Assert.Equal(2, azdo2.TimelineCallCount); + exitCode2.Should().Be(0); + azdo2.TimelineCallCount.Should().Be(2); // Only helix-windows uploaded (helix-linux was already processed) - Assert.Equal(["helix-windows"], azdo2.UploadedJobNames); - Assert.Single(azdo2.CreatedTestRuns); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-windows"]); + azdo2.CreatedTestRuns.Should().ContainSingle(); } // ----------------------------------------------------------------------- @@ -1500,18 +1498,18 @@ public async Task RetryAttempt2_ResubmitsFailedWorkItems_ResubmissionPasses_Exit int exitCode = await runner.RunAsync(CancellationToken.None); // Resubmission healed the failures → exit 0 - Assert.Equal(0, exitCode); + exitCode.Should().Be(0); // Test result upload is independent from retry: original results upload before the resubmission. - Assert.Equal(new[] {"helix-linux", "helix-linux-resub"}.Order(), azdo.UploadedJobNames.Order()); - Assert.Equal(2, azdo.CreatedTestRuns.Count); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); + azdo.CreatedTestRuns.Should().HaveCount(2); // Only the 2 failed items were resubmitted (not the passing one) - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-linux", helix.Resubmissions[0].OriginalJob); - Assert.Equal(2, helix.Resubmissions[0].FailedItems.Count); - Assert.Contains("wi-fail-1", helix.Resubmissions[0].FailedItems); - Assert.Contains("wi-fail-2", helix.Resubmissions[0].FailedItems); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); + helix.Resubmissions[0].FailedItems.Should().HaveCount(2); + helix.Resubmissions[0].FailedItems.Should().Contain("wi-fail-1"); + helix.Resubmissions[0].FailedItems.Should().Contain("wi-fail-2"); } /// @@ -1553,8 +1551,8 @@ public async Task RetryAttempt2_ResubmitsFailedWorkItems_ResubmissionAlsoFails_E int exitCode = await runner.RunAsync(CancellationToken.None); // Resubmission also failed → exit 1 - Assert.Equal(1, exitCode); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(1); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1626,24 +1624,23 @@ public async Task RetryAttempt2_MultipleJobs_OnlyFailedItemsResubmitted_ExitZero var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); + exitCode.Should().Be(0); // Test result upload is independent from retry: originals upload before resubmissions. - Assert.Equal(4, azdo.CreatedTestRuns.Count); - Assert.Equal( - new[] { "helix-linux", "helix-windows", "helix-linux-resub", "helix-windows-resub" }.Order(), - azdo.UploadedJobNames.Order()); + azdo.CreatedTestRuns.Should().HaveCount(4); + azdo.UploadedJobNames.Should().BeEquivalentTo( + ["helix-linux", "helix-windows", "helix-linux-resub", "helix-windows-resub"]); // Each original job only had its failed items resubmitted - Assert.Equal(2, helix.Resubmissions.Count); + helix.Resubmissions.Should().HaveCount(2); var linuxResub = helix.Resubmissions.Single(r => r.OriginalJob == "helix-linux"); - Assert.Single(linuxResub.FailedItems); // only "linux-fail" - Assert.Contains("linux-fail", linuxResub.FailedItems); + linuxResub.FailedItems.Should().ContainSingle(); // only "linux-fail" + linuxResub.FailedItems.Should().Contain("linux-fail"); var windowsResub = helix.Resubmissions.Single(r => r.OriginalJob == "helix-windows"); - Assert.Equal(2, windowsResub.FailedItems.Count); - Assert.Contains("win-fail-1", windowsResub.FailedItems); - Assert.Contains("win-fail-2", windowsResub.FailedItems); + windowsResub.FailedItems.Should().HaveCount(2); + windowsResub.FailedItems.Should().Contain("win-fail-1"); + windowsResub.FailedItems.Should().Contain("win-fail-2"); } /// @@ -1671,9 +1668,9 @@ public async Task HelixJobFailsAfterMonitorEntry_IsNotResubmittedUntilNextEntry( var runner1 = CreateRunner(azdo1, helix1); int exitCode1 = await runner1.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode1); - Assert.Empty(helix1.Resubmissions); - Assert.Equal(["helix-linux"], azdo1.UploadedJobNames); + exitCode1.Should().Be(1); + helix1.Resubmissions.Should().BeEmpty(); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-linux"); @@ -1699,11 +1696,11 @@ public async Task HelixJobFailsAfterMonitorEntry_IsNotResubmittedUntilNextEntry( var runner2 = CreateRunner(azdo2, helix2); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode2); - Assert.Single(helix2.Resubmissions); - Assert.Equal("helix-linux", helix2.Resubmissions[0].OriginalJob); - Assert.Equal(["wi-fail"], helix2.Resubmissions[0].FailedItems); - Assert.Equal(["helix-linux-resub"], azdo2.UploadedJobNames); + exitCode2.Should().Be(0); + helix2.Resubmissions.Should().ContainSingle(); + helix2.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); + helix2.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-fail"]); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-resub"]); } /// @@ -1735,9 +1732,9 @@ public async Task NewerPassedIncarnationExistsOnEntry_DoesNotResubmitOlderFailur var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1779,9 +1776,9 @@ public async Task NewerRunningIncarnationExistsOnEntry_DoesNotResubmitOlderFailu var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1826,13 +1823,12 @@ public async Task LatestCompletedIncarnationPartiallyHealed_ResubmitsOnlyRemaini var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-linux-resub", helix.Resubmissions[0].OriginalJob); - Assert.Equal(["wi-2"], helix.Resubmissions[0].FailedItems); - Assert.Equal( - new[] { "helix-linux", "helix-linux-resub", "helix-linux-resub-2" }.Order(), - azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux-resub"); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-2"]); + azdo.UploadedJobNames.Should().BeEquivalentTo( + ["helix-linux", "helix-linux-resub", "helix-linux-resub-2"]); } /// @@ -1862,8 +1858,8 @@ public async Task CompletedHelixJobsReturnedOutOfOrder_UploadsOldToNew() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1942,9 +1938,9 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd var runner1 = CreateRunner(azdo1, helix1); int exitCode1 = await runner1.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode1); - Assert.Equal(["helix-a", "helix-b"], azdo1.UploadedJobNames); - Assert.Empty(helix1.Resubmissions); + exitCode1.Should().Be(1); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-a", "helix-b"]); + helix1.Resubmissions.Should().BeEmpty(); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-a"); @@ -1987,13 +1983,13 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd var runner2 = CreateRunner(azdo2, helix2); int exitCode2 = await runner2.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode2); - Assert.Equal(2, azdo2.TimelineCallCount); - Assert.Equal(new[] { "helix-b", "helix-a-resub", "helix-b-resub" }.Order(), azdo2.UploadedJobNames.Order()); + exitCode2.Should().Be(1); + azdo2.TimelineCallCount.Should().Be(2); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); - Assert.Equal(2, helix2.Resubmissions.Count); - Assert.Equal(["a-fail"], helix2.Resubmissions.Single(r => r.OriginalJob == "helix-a").FailedItems); - Assert.Equal(["b-fail"], helix2.Resubmissions.Single(r => r.OriginalJob == "helix-b").FailedItems); + helix2.Resubmissions.Should().HaveCount(2); + helix2.Resubmissions.Single(r => r.OriginalJob == "helix-a").FailedItems.Should().BeEquivalentTo(["a-fail"]); + helix2.Resubmissions.Single(r => r.OriginalJob == "helix-b").FailedItems.Should().BeEquivalentTo(["b-fail"]); var azdo3 = new FakeAzureDevOpsService(); azdo3.WithPreviouslyProcessedJob("helix-a"); @@ -2044,11 +2040,11 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd var runner3 = CreateRunner(azdo3, helix3); int exitCode3 = await runner3.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode3); - Assert.Single(helix3.Resubmissions); - Assert.Equal("helix-b-resub", helix3.Resubmissions[0].OriginalJob); - Assert.Equal(["b-fail"], helix3.Resubmissions[0].FailedItems); - Assert.Equal(["helix-b-resub-2"], azdo3.UploadedJobNames); + exitCode3.Should().Be(0); + helix3.Resubmissions.Should().ContainSingle(); + helix3.Resubmissions[0].OriginalJob.Should().Be("helix-b-resub"); + helix3.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["b-fail"]); + azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b-resub-2"]); } /// @@ -2097,17 +2093,17 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() // assertion below is deterministic. Otherwise the background upload task // (bound to the runner's cancellation token) races against cts1.Cancel(). Task completed = await Task.WhenAny(azdo1.UploadCompleted.Task, Task.Delay(TimeSpan.FromSeconds(5))); - Assert.Same(azdo1.UploadCompleted.Task, completed); + completed.Should().BeSameAs(azdo1.UploadCompleted.Task); cts1.Cancel(); }); int exitCode1 = await runner1.RunAsync(cts1.Token); - Assert.Equal(1, exitCode1); - Assert.Single(helix1.Resubmissions); - Assert.Equal("helix-a", helix1.Resubmissions[0].OriginalJob); - Assert.Equal(["a-fail"], helix1.Resubmissions[0].FailedItems); - Assert.Equal(["helix-a"], azdo1.UploadedJobNames); + exitCode1.Should().Be(1); + helix1.Resubmissions.Should().ContainSingle(); + helix1.Resubmissions[0].OriginalJob.Should().Be("helix-a"); + helix1.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["a-fail"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-a"); @@ -2145,9 +2141,9 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() int exitCode2 = await runner2.RunAsync(cts2.Token); - Assert.Equal(1, exitCode2); - Assert.Empty(helix2.Resubmissions); - Assert.Empty(azdo2.UploadedJobNames); + exitCode2.Should().Be(1); + helix2.Resubmissions.Should().BeEmpty(); + azdo2.UploadedJobNames.Should().BeEmpty(); var azdo3 = new FakeAzureDevOpsService(); azdo3.WithPreviouslyProcessedJob("helix-a"); @@ -2204,11 +2200,11 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() var runner3 = CreateRunner(azdo3, helix3); int exitCode3 = await runner3.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode3); - Assert.Single(helix3.Resubmissions); - Assert.Equal("helix-b", helix3.Resubmissions[0].OriginalJob); - Assert.Equal(["b-fail"], helix3.Resubmissions[0].FailedItems); - Assert.Equal(new[] { "helix-b", "helix-a-resub", "helix-b-resub" }.Order(), azdo3.UploadedJobNames.Order()); + exitCode3.Should().Be(0); + helix3.Resubmissions.Should().ContainSingle(); + helix3.Resubmissions[0].OriginalJob.Should().Be("helix-b"); + helix3.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["b-fail"]); + azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); } /// @@ -2235,11 +2231,11 @@ public async Task Attempt1_ResubmitsFailedWorkItemsFoundOnEntry_ExitOne() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-linux", helix.Resubmissions[0].OriginalJob); - Assert.Equal(["wi-fail"], helix.Resubmissions[0].FailedItems); - Assert.Single(azdo.UploadedJobNames); // only original + exitCode.Should().Be(1); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-fail"]); + azdo.UploadedJobNames.Should().ContainSingle(); // only original } /// @@ -2268,11 +2264,11 @@ public async Task FailedAzdoSubmitter_WithoutSystemJobNameLink_IsNotIgnored() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-a", helix.Resubmissions[0].OriginalJob); - Assert.Equal(["a-fail"], helix.Resubmissions[0].FailedItems); - Assert.Equal(["helix-a"], azdo.UploadedJobNames); + exitCode.Should().Be(1); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-a"); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["a-fail"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); } /// @@ -2300,11 +2296,11 @@ public async Task ResubmitReturnsNull_FailedSubmitterIsNotIgnored() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-a", helix.Resubmissions[0].OriginalJob); - Assert.Null(helix.Resubmissions[0].NewJob); - Assert.Equal(["helix-a"], azdo.UploadedJobNames); + exitCode.Should().Be(1); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-a"); + helix.Resubmissions[0].NewJob.Should().BeNull(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); } /// @@ -2352,9 +2348,9 @@ public async Task LatestRunningAfterMultipleCompletedFailures_DoesNotResubmitOld var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-r1", "helix-linux-r2" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-r1", "helix-linux-r2"]); } /// @@ -2374,8 +2370,8 @@ public async Task CanceledAzdoJob_FailsMonitor() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.Empty(azdo.UploadedJobNames); + exitCode.Should().Be(1); + azdo.UploadedJobNames.Should().BeEmpty(); } /// @@ -2407,10 +2403,10 @@ public async Task SameCompletedHelixJobSeenAcrossPolls_UploadsOnce() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(["helix-linux"], azdo.UploadedJobNames); - Assert.Single(azdo.CreatedTestRuns); - Assert.Single(azdo.CompletedTestRunIds); + exitCode.Should().Be(0); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); + azdo.CreatedTestRuns.Should().ContainSingle(); + azdo.CompletedTestRunIds.Should().ContainSingle(); } /// @@ -2448,10 +2444,10 @@ public async Task PreviouslyProcessedFailedJob_StillContributesToRetryAndPassFai var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-linux", helix.Resubmissions[0].OriginalJob); - Assert.Equal(["helix-linux-resub"], azdo.UploadedJobNames); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-resub"]); } /// @@ -2484,9 +2480,9 @@ public async Task PartiallyProcessedLineage_UploadsOnlyUnprocessedOldToNew() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux-r1", "helix-linux-r2" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-r1", "helix-linux-r2"]); } /// @@ -2539,10 +2535,10 @@ public async Task StageScopedMonitor_LineageCrossesStageBoundary_IgnoresOutOfSta var runner = CreateRunner(azdo, helix, stageName: "Test"); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal("helix-test", helix.Resubmissions[0].OriginalJob); - Assert.Equal(new[] { "helix-test", "helix-test-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-test"); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-test", "helix-test-resub"]); } /// @@ -2567,8 +2563,8 @@ public async Task HelixJobStatusFailed_IsTreatedAsCompleted() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Equal(["helix-linux"], azdo.UploadedJobNames); + exitCode.Should().Be(0); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -2603,10 +2599,10 @@ public async Task FinishedHelixJob_WithNonFinishedWorkItemState_IsFailure() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); - Assert.Single(helix.Resubmissions); - Assert.Equal(["wi-timeout"], helix.Resubmissions[0].FailedItems); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-timeout"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } [Fact] @@ -2636,18 +2632,18 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() var runner = CreateRunner(azdo, helix, logger: logger); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(1, exitCode); - Assert.DoesNotContain(logger.Messages, message => + exitCode.Should().Be(1); + logger.Messages.Should().NotContain(message => message.Contains("wi-pass", StringComparison.Ordinal) && message.Contains("https://helix.example/wi-pass/console", StringComparison.Ordinal)); - Assert.Contains(logger.Messages, message => + logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); - Assert.Contains(logger.Messages, message => + logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); - Assert.DoesNotContain(logger.Messages, message => + logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } @@ -2686,14 +2682,14 @@ public async Task LoopStatus_LogsAggregateHelixJobWorkItemCounts() int exitCode = await runner.RunAsync(cts.Token); - Assert.Equal(1, exitCode); - Assert.Contains(logger.Messages, message => + exitCode.Should().Be(1); + logger.Messages.Should().Contain(message => message.Contains("0 processed / 0 completed / 1 running / 0 waiting jobs", StringComparison.Ordinal)); - Assert.Contains(logger.Messages, message => + logger.Messages.Should().Contain(message => message.Contains("0 processed / 0 completed / 3 running / 0 waiting work items", StringComparison.Ordinal)); - Assert.DoesNotContain(logger.Messages, message => + logger.Messages.Should().NotContain(message => message.Contains("Helix job details:", StringComparison.Ordinal)); - Assert.Contains(logger.Messages, message => + logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-2' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-2/console", StringComparison.Ordinal)); } @@ -2729,8 +2725,8 @@ public async Task LoopStatus_VerboseLogsFullHelixJobWorkItemList() int exitCode = await runner.RunAsync(cts.Token); - Assert.Equal(1, exitCode); - Assert.Contains(logger.Messages, message => + exitCode.Should().Be(1); + logger.Messages.Should().Contain(message => message.Contains("Helix job details:", StringComparison.Ordinal) && message.Contains("└─ 🧪 Helix job helix-linux [Running]", StringComparison.Ordinal) && message.Contains(" ├─ wi-01 (Running)", StringComparison.Ordinal)