From 75b3b73b1207f93446a276e5af3dd0f01995dbaa Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Tue, 2 Jun 2026 14:46:59 +0200 Subject: [PATCH 1/3] JobMonitor: Use fluent assertions to handle random order of results --- .../AzureDevOpsServiceTests.cs | 3 +- .../JobMonitorRunnerTests.cs | 46 +++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) 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..68ebdefa244 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; @@ -66,7 +67,7 @@ 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); 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 6cd7053e315..f2c0eff0070 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; @@ -665,9 +666,8 @@ public async Task StageRerun_UploadsNewHelixWorkItemsWithoutReuploadingPreviousW 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo( + ["old-helix-linux", "old-helix-windows", "new-helix-linux", "new-helix-windows"]); var uploadedWorkItems = azdo.UploadedResultsByRunId .Values @@ -1058,7 +1058,7 @@ public async Task StageScopedMonitor_OnRetry_IgnoresDefaultRefNameMonitorJobUnde 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); static void AddRetriedStageTimeline(FakeAzureDevOpsService azdo) { @@ -1212,9 +1212,7 @@ public async Task MonitorTimesOut_CancelsLatestInFlightHelixJobs() 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()); + helix.CanceledJobs.Should().BeEquivalentTo(["helix-new-attempt", "helix-running", "helix-waiting"]); } /// @@ -1282,7 +1280,7 @@ public async Task MonitorTimesOut_DoesNotReportOrCancelJobsThatFinishedAfterFirs Assert.DoesNotContain("helix-good", timeoutMessage, StringComparison.Ordinal); // 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"]); } /// @@ -1450,7 +1448,7 @@ public async Task RetryAttempt2_ResubmitsFailedWorkItems_ResubmissionPasses_Exit Assert.Equal(0, exitCode); // 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); Assert.Equal(2, azdo.CreatedTestRuns.Count); // Only the 2 failed items were resubmitted (not the passing one) @@ -1501,7 +1499,7 @@ public async Task RetryAttempt2_ResubmitsFailedWorkItems_ResubmissionAlsoFails_E // Resubmission also failed → exit 1 Assert.Equal(1, exitCode); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1577,9 +1575,8 @@ public async Task RetryAttempt2_MultipleJobs_OnlyFailedItemsResubmitted_ExitZero // 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.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); @@ -1684,7 +1681,7 @@ public async Task NewerPassedIncarnationExistsOnEntry_DoesNotResubmitOlderFailur Assert.Equal(0, exitCode); Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1728,7 +1725,7 @@ public async Task NewerRunningIncarnationExistsOnEntry_DoesNotResubmitOlderFailu Assert.Equal(0, exitCode); Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1777,9 +1774,8 @@ public async Task LatestCompletedIncarnationPartiallyHealed_ResubmitsOnlyRemaini 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo( + ["helix-linux", "helix-linux-resub", "helix-linux-resub-2"]); } /// @@ -1810,7 +1806,7 @@ public async Task CompletedHelixJobsReturnedOutOfOrder_UploadsOldToNew() int exitCode = await runner.RunAsync(CancellationToken.None); Assert.Equal(0, exitCode); - Assert.Equal(new[] { "helix-linux", "helix-linux-resub" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } /// @@ -1936,7 +1932,7 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd Assert.Equal(1, exitCode2); Assert.Equal(2, azdo2.TimelineCallCount); - Assert.Equal(new[] { "helix-b", "helix-a-resub", "helix-b-resub" }.Order(), azdo2.UploadedJobNames.Order()); + 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); @@ -2151,7 +2147,7 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() 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()); + azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); } /// @@ -2297,7 +2293,7 @@ public async Task LatestRunningAfterMultipleCompletedFailures_DoesNotResubmitOld Assert.Equal(0, exitCode); Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux", "helix-linux-r1", "helix-linux-r2" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-r1", "helix-linux-r2"]); } /// @@ -2429,7 +2425,7 @@ public async Task PartiallyProcessedLineage_UploadsOnlyUnprocessedOldToNew() Assert.Equal(0, exitCode); Assert.Empty(helix.Resubmissions); - Assert.Equal(new[] { "helix-linux-r1", "helix-linux-r2" }.Order(), azdo.UploadedJobNames.Order()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-r1", "helix-linux-r2"]); } /// @@ -2485,7 +2481,7 @@ public async Task StageScopedMonitor_LineageCrossesStageBoundary_IgnoresOutOfSta 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-test", "helix-test-resub"]); } /// @@ -2549,7 +2545,7 @@ public async Task FinishedHelixJob_WithNonFinishedWorkItemState_IsFailure() 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()); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } [Fact] From 026a317864c736c96d076426947908997763388b Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Tue, 2 Jun 2026 14:59:37 +0200 Subject: [PATCH 2/3] Use fluent assertions everywhere --- .../AzureDevOpsServiceTests.cs | 42 +- .../JobMonitorRunnerTests.cs | 438 +++++++++--------- 2 files changed, 240 insertions(+), 240 deletions(-) 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 68ebdefa244..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 @@ -31,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()); - - 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")); + 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(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] @@ -70,8 +70,8 @@ public async Task GetProcessedHelixJobNamesAsync_RecoversFromTestRunNameMarker() 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] @@ -86,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] @@ -111,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 f2c0eff0070..41ee19fcb88 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 @@ -55,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(); } /// @@ -105,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(); } /// @@ -147,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(); } /// @@ -189,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(); } /// @@ -244,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. @@ -281,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(); } /// @@ -354,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().Equal(["helix-linux-tests"]); } [Fact] @@ -397,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().Equal(["helix-linux"]); + azdo.CompletedTestRunIds.Should().ContainSingle(); + logger.Messages.Should().Contain(message => message.Contains("2 test results for job 'helix-linux' processed.", StringComparison.Ordinal)); } @@ -448,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().Equal(["helix-linux"]); } /// @@ -491,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().Equal(["helix-linux"]); } /// @@ -563,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"); } /// @@ -608,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); } [Fact] @@ -664,8 +664,8 @@ 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); + exitCode1.Should().Be(0); + exitCode2.Should().Be(0); azdo.UploadedJobNames.Should().BeEquivalentTo( ["old-helix-linux", "old-helix-windows", "new-helix-linux", "new-helix-windows"]); @@ -675,13 +675,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"); } /// @@ -719,9 +719,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(); } /// @@ -751,10 +751,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(); } /// @@ -785,10 +785,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(); } /// @@ -815,16 +815,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().Equal(["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().Equal(["helix-build-windows"]); static void AddStageTimelineFrames(FakeAzureDevOpsService azdo) { @@ -941,9 +941,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().Equal(["helix-test-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-test-linux"); @@ -982,12 +982,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().Equal(["test-fail"]); + helix2.Resubmissions.Should().NotContain(r => r.OriginalJob == "helix-build-windows"); + azdo2.UploadedJobNames.Should().Equal(["helix-test-linux-resub"]); static void AddSingleMonitorStageTimeline(FakeAzureDevOpsService azdo, int attempt) { @@ -1053,11 +1053,11 @@ 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); + 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().Equal(["linux-fail"]); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); static void AddRetriedStageTimeline(FakeAzureDevOpsService azdo) @@ -1144,8 +1144,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().Equal(["helix-linux"]); // --- Second run: monitor relaunched, helix-linux already processed --- var azdo2 = new FakeAzureDevOpsService(); @@ -1170,9 +1170,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().Equal(["helix-windows"]); + azdo2.CreatedTestRuns.Should().ContainSingle(); } [Fact] @@ -1204,14 +1204,14 @@ 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); + exitCode.Should().Be(1); + azdo.UploadedJobNames.Should().Equal(["helix-finished"]); helix.CanceledJobs.Should().BeEquivalentTo(["helix-new-attempt", "helix-running", "helix-waiting"]); } @@ -1263,21 +1263,21 @@ 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. helix.CanceledJobs.Should().BeEquivalentTo(["helix-stuck"]); @@ -1337,8 +1337,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().Equal(["helix-linux"]); // --- Second run: monitor relaunched --- // Test Windows has now completed and submitted helix-windows. @@ -1377,11 +1377,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().Equal(["helix-windows"]); + azdo2.CreatedTestRuns.Should().ContainSingle(); } // ----------------------------------------------------------------------- @@ -1445,18 +1445,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. azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); - Assert.Equal(2, azdo.CreatedTestRuns.Count); + 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"); } /// @@ -1498,7 +1498,7 @@ public async Task RetryAttempt2_ResubmitsFailedWorkItems_ResubmissionAlsoFails_E int exitCode = await runner.RunAsync(CancellationToken.None); // Resubmission also failed → exit 1 - Assert.Equal(1, exitCode); + exitCode.Should().Be(1); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } @@ -1571,23 +1571,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); + 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"); } /// @@ -1615,9 +1615,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().Equal(["helix-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-linux"); @@ -1643,11 +1643,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().Equal(["wi-fail"]); + azdo2.UploadedJobNames.Should().Equal(["helix-linux-resub"]); } /// @@ -1679,8 +1679,8 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } @@ -1723,8 +1723,8 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } @@ -1770,10 +1770,10 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux-resub"); + helix.Resubmissions[0].FailedItems.Should().Equal(["wi-2"]); azdo.UploadedJobNames.Should().BeEquivalentTo( ["helix-linux", "helix-linux-resub", "helix-linux-resub-2"]); } @@ -1805,7 +1805,7 @@ public async Task CompletedHelixJobsReturnedOutOfOrder_UploadsOldToNew() var runner = CreateRunner(azdo, helix); int exitCode = await runner.RunAsync(CancellationToken.None); - Assert.Equal(0, exitCode); + exitCode.Should().Be(0); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } @@ -1885,9 +1885,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().Equal(["helix-a", "helix-b"]); + helix1.Resubmissions.Should().BeEmpty(); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-a"); @@ -1930,13 +1930,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); + 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().Equal(["a-fail"]); + helix2.Resubmissions.Single(r => r.OriginalJob == "helix-b").FailedItems.Should().Equal(["b-fail"]); var azdo3 = new FakeAzureDevOpsService(); azdo3.WithPreviouslyProcessedJob("helix-a"); @@ -1987,11 +1987,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().Equal(["b-fail"]); + azdo3.UploadedJobNames.Should().Equal(["helix-b-resub-2"]); } /// @@ -2042,11 +2042,11 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() 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().Equal(["a-fail"]); + azdo1.UploadedJobNames.Should().Equal(["helix-a"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-a"); @@ -2084,9 +2084,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"); @@ -2143,10 +2143,10 @@ 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); + exitCode3.Should().Be(0); + helix3.Resubmissions.Should().ContainSingle(); + helix3.Resubmissions[0].OriginalJob.Should().Be("helix-b"); + helix3.Resubmissions[0].FailedItems.Should().Equal(["b-fail"]); azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); } @@ -2174,11 +2174,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().Equal(["wi-fail"]); + azdo.UploadedJobNames.Should().ContainSingle(); // only original } /// @@ -2207,11 +2207,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().Equal(["a-fail"]); + azdo.UploadedJobNames.Should().Equal(["helix-a"]); } /// @@ -2239,11 +2239,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().Equal(["helix-a"]); } /// @@ -2291,8 +2291,8 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-r1", "helix-linux-r2"]); } @@ -2313,8 +2313,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(); } /// @@ -2346,10 +2346,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().Equal(["helix-linux"]); + azdo.CreatedTestRuns.Should().ContainSingle(); + azdo.CompletedTestRunIds.Should().ContainSingle(); } /// @@ -2387,10 +2387,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().Equal(["helix-linux-resub"]); } /// @@ -2423,8 +2423,8 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().BeEmpty(); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-r1", "helix-linux-r2"]); } @@ -2478,9 +2478,9 @@ 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); + 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"]); } @@ -2506,8 +2506,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().Equal(["helix-linux"]); } /// @@ -2542,9 +2542,9 @@ 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); + exitCode.Should().Be(0); + helix.Resubmissions.Should().ContainSingle(); + helix.Resubmissions[0].FailedItems.Should().Equal(["wi-timeout"]); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); } @@ -2575,18 +2575,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)); } @@ -2625,14 +2625,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)); } @@ -2668,8 +2668,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) From 0fb3b9fd0d13e684fee056a597c9700fd468a091 Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Wed, 3 Jun 2026 12:12:02 +0200 Subject: [PATCH 3/3] Use order insensitive comparison everywhere --- .../JobMonitorRunnerTests.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) 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 304a7ed8b29..43c57029189 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 @@ -364,7 +364,7 @@ public async Task BuildJobSubmitsHelixWork_WorkItemsPassed_ResultsUploaded_ExitZ azdo.CompletedTestRunIds.Should().ContainSingle(); // Test results uploaded for the Helix job - azdo.UploadedJobNames.Should().Equal(["helix-linux-tests"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-tests"]); } [Fact] @@ -407,7 +407,7 @@ public async Task CompletedHelixJob_QueuesTestResultUploadWithoutBlockingNextPol exitCode.Should().Be(0); delayedBeforeUploadCompleted.Should().BeTrue(); azdo.TimelineCallCount.Should().Be(2); - azdo.UploadedJobNames.Should().Equal(["helix-linux"]); + 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)); @@ -453,7 +453,7 @@ public async Task PassedHelixWork_UploadFailsOnce_RetriesAndProcessesResults() delayCount.Should().Be(1); azdo.CreatedTestRuns.Should().ContainSingle(); azdo.CompletedTestRunIds.Should().ContainSingle(); - azdo.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -495,7 +495,7 @@ public async Task MultipleWorkItems_FinishAtDifferentTimes_AllPass_ExitZero() azdo.TimelineCallCount.Should().Be(4); azdo.CreatedTestRuns.Should().ContainSingle(); azdo.CompletedTestRunIds.Should().ContainSingle(); - azdo.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -861,11 +861,11 @@ public async Task StageScopedMonitors_ShutDownWhenTheirOwnStageWorkCompletes() // Test stage finishes in frame 2 while Build stage and its Helix job are still running. testAzdo.TimelineCallCount.Should().Be(2); - testAzdo.UploadedJobNames.Should().Equal(["helix-test-linux"]); + testAzdo.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux"]); // Build stage pipeline work finishes in frame 3, but its Helix job finishes in frame 4. buildAzdo.TimelineCallCount.Should().Be(4); - buildAzdo.UploadedJobNames.Should().Equal(["helix-build-windows"]); + buildAzdo.UploadedJobNames.Should().BeEquivalentTo(["helix-build-windows"]); static void AddStageTimelineFrames(FakeAzureDevOpsService azdo) { @@ -984,7 +984,7 @@ public async Task StageScopedMonitor_OnStageRetry_OnlyRetriesFailedHelixWorkFrom exitCode1.Should().Be(1); helix1.Resubmissions.Should().BeEmpty(); - azdo1.UploadedJobNames.Should().Equal(["helix-test-linux"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-test-linux"); @@ -1026,9 +1026,9 @@ public async Task StageScopedMonitor_OnStageRetry_OnlyRetriesFailedHelixWorkFrom exitCode2.Should().Be(0); helix2.Resubmissions.Should().ContainSingle(); helix2.Resubmissions[0].OriginalJob.Should().Be("helix-test-linux"); - helix2.Resubmissions[0].FailedItems.Should().Equal(["test-fail"]); + helix2.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["test-fail"]); helix2.Resubmissions.Should().NotContain(r => r.OriginalJob == "helix-build-windows"); - azdo2.UploadedJobNames.Should().Equal(["helix-test-linux-resub"]); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-test-linux-resub"]); static void AddSingleMonitorStageTimeline(FakeAzureDevOpsService azdo, int attempt) { @@ -1098,7 +1098,7 @@ public async Task StageScopedMonitor_OnRetry_IgnoresDefaultRefNameMonitorJobUnde delayCount.Should().Be(1); helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); - helix.Resubmissions[0].FailedItems.Should().Equal(["linux-fail"]); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["linux-fail"]); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); static void AddRetriedStageTimeline(FakeAzureDevOpsService azdo) @@ -1186,7 +1186,7 @@ public async Task MonitorTimesOut_Relaunched_UploadsRemainingResults() // Timed out → exit 1. helix-linux was uploaded, helix-windows was not. exitCode1.Should().Be(1); - azdo1.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); // --- Second run: monitor relaunched, helix-linux already processed --- var azdo2 = new FakeAzureDevOpsService(); @@ -1212,7 +1212,7 @@ public async Task MonitorTimesOut_Relaunched_UploadsRemainingResults() // helix-linux skipped (already processed), helix-windows uploaded → exit 0 exitCode2.Should().Be(0); - azdo2.UploadedJobNames.Should().Equal(["helix-windows"]); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-windows"]); azdo2.CreatedTestRuns.Should().ContainSingle(); } @@ -1253,7 +1253,7 @@ public async Task MonitorTimesOut_CancelsLatestInFlightHelixJobs() int exitCode = await runner.RunAsync(cts.Token); exitCode.Should().Be(1); - azdo.UploadedJobNames.Should().Equal(["helix-finished"]); + 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) @@ -1389,7 +1389,7 @@ public async Task MonitorTimesOut_PartialProgress_Relaunched_CompletesSuccessful int exitCode1 = await runner1.RunAsync(cts.Token); exitCode1.Should().Be(1); - azdo1.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); // --- Second run: monitor relaunched --- // Test Windows has now completed and submitted helix-windows. @@ -1431,7 +1431,7 @@ public async Task MonitorTimesOut_PartialProgress_Relaunched_CompletesSuccessful exitCode2.Should().Be(0); azdo2.TimelineCallCount.Should().Be(2); // Only helix-windows uploaded (helix-linux was already processed) - azdo2.UploadedJobNames.Should().Equal(["helix-windows"]); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-windows"]); azdo2.CreatedTestRuns.Should().ContainSingle(); } @@ -1668,7 +1668,7 @@ public async Task HelixJobFailsAfterMonitorEntry_IsNotResubmittedUntilNextEntry( exitCode1.Should().Be(1); helix1.Resubmissions.Should().BeEmpty(); - azdo1.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-linux"); @@ -1697,8 +1697,8 @@ public async Task HelixJobFailsAfterMonitorEntry_IsNotResubmittedUntilNextEntry( exitCode2.Should().Be(0); helix2.Resubmissions.Should().ContainSingle(); helix2.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); - helix2.Resubmissions[0].FailedItems.Should().Equal(["wi-fail"]); - azdo2.UploadedJobNames.Should().Equal(["helix-linux-resub"]); + helix2.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-fail"]); + azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-resub"]); } /// @@ -1824,7 +1824,7 @@ public async Task LatestCompletedIncarnationPartiallyHealed_ResubmitsOnlyRemaini exitCode.Should().Be(0); helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux-resub"); - helix.Resubmissions[0].FailedItems.Should().Equal(["wi-2"]); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-2"]); azdo.UploadedJobNames.Should().BeEquivalentTo( ["helix-linux", "helix-linux-resub", "helix-linux-resub-2"]); } @@ -1937,7 +1937,7 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd int exitCode1 = await runner1.RunAsync(CancellationToken.None); exitCode1.Should().Be(1); - azdo1.UploadedJobNames.Should().Equal(["helix-a", "helix-b"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-a", "helix-b"]); helix1.Resubmissions.Should().BeEmpty(); var azdo2 = new FakeAzureDevOpsService(); @@ -1986,8 +1986,8 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd azdo2.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); helix2.Resubmissions.Should().HaveCount(2); - helix2.Resubmissions.Single(r => r.OriginalJob == "helix-a").FailedItems.Should().Equal(["a-fail"]); - helix2.Resubmissions.Single(r => r.OriginalJob == "helix-b").FailedItems.Should().Equal(["b-fail"]); + 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"); @@ -2041,8 +2041,8 @@ public async Task RetryAfterMixedAzDOAndHelixFailures_RestartsFailedHelixWorkAnd exitCode3.Should().Be(0); helix3.Resubmissions.Should().ContainSingle(); helix3.Resubmissions[0].OriginalJob.Should().Be("helix-b-resub"); - helix3.Resubmissions[0].FailedItems.Should().Equal(["b-fail"]); - azdo3.UploadedJobNames.Should().Equal(["helix-b-resub-2"]); + helix3.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["b-fail"]); + azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b-resub-2"]); } /// @@ -2100,8 +2100,8 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() exitCode1.Should().Be(1); helix1.Resubmissions.Should().ContainSingle(); helix1.Resubmissions[0].OriginalJob.Should().Be("helix-a"); - helix1.Resubmissions[0].FailedItems.Should().Equal(["a-fail"]); - azdo1.UploadedJobNames.Should().Equal(["helix-a"]); + helix1.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["a-fail"]); + azdo1.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); var azdo2 = new FakeAzureDevOpsService(); azdo2.WithPreviouslyProcessedJob("helix-a"); @@ -2201,7 +2201,7 @@ public async Task RetryOnEntryWithCrashes_ResubmitsOnlyLatestFailedWork() exitCode3.Should().Be(0); helix3.Resubmissions.Should().ContainSingle(); helix3.Resubmissions[0].OriginalJob.Should().Be("helix-b"); - helix3.Resubmissions[0].FailedItems.Should().Equal(["b-fail"]); + helix3.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["b-fail"]); azdo3.UploadedJobNames.Should().BeEquivalentTo(["helix-b", "helix-a-resub", "helix-b-resub"]); } @@ -2232,7 +2232,7 @@ public async Task Attempt1_ResubmitsFailedWorkItemsFoundOnEntry_ExitOne() exitCode.Should().Be(1); helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); - helix.Resubmissions[0].FailedItems.Should().Equal(["wi-fail"]); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-fail"]); azdo.UploadedJobNames.Should().ContainSingle(); // only original } @@ -2265,8 +2265,8 @@ public async Task FailedAzdoSubmitter_WithoutSystemJobNameLink_IsNotIgnored() exitCode.Should().Be(1); helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-a"); - helix.Resubmissions[0].FailedItems.Should().Equal(["a-fail"]); - azdo.UploadedJobNames.Should().Equal(["helix-a"]); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["a-fail"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); } /// @@ -2298,7 +2298,7 @@ public async Task ResubmitReturnsNull_FailedSubmitterIsNotIgnored() helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-a"); helix.Resubmissions[0].NewJob.Should().BeNull(); - azdo.UploadedJobNames.Should().Equal(["helix-a"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-a"]); } /// @@ -2402,7 +2402,7 @@ public async Task SameCompletedHelixJobSeenAcrossPolls_UploadsOnce() int exitCode = await runner.RunAsync(CancellationToken.None); exitCode.Should().Be(0); - azdo.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); azdo.CreatedTestRuns.Should().ContainSingle(); azdo.CompletedTestRunIds.Should().ContainSingle(); } @@ -2445,7 +2445,7 @@ public async Task PreviouslyProcessedFailedJob_StillContributesToRetryAndPassFai exitCode.Should().Be(0); helix.Resubmissions.Should().ContainSingle(); helix.Resubmissions[0].OriginalJob.Should().Be("helix-linux"); - azdo.UploadedJobNames.Should().Equal(["helix-linux-resub"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux-resub"]); } /// @@ -2562,7 +2562,7 @@ public async Task HelixJobStatusFailed_IsTreatedAsCompleted() int exitCode = await runner.RunAsync(CancellationToken.None); exitCode.Should().Be(0); - azdo.UploadedJobNames.Should().Equal(["helix-linux"]); + azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux"]); } /// @@ -2599,7 +2599,7 @@ public async Task FinishedHelixJob_WithNonFinishedWorkItemState_IsFailure() exitCode.Should().Be(0); helix.Resubmissions.Should().ContainSingle(); - helix.Resubmissions[0].FailedItems.Should().Equal(["wi-timeout"]); + helix.Resubmissions[0].FailedItems.Should().BeEquivalentTo(["wi-timeout"]); azdo.UploadedJobNames.Should().BeEquivalentTo(["helix-linux", "helix-linux-resub"]); }