Make vacuous CI guards capable of failing - #19177
Make vacuous CI guards capable of failing#19177Adam Ratzman (adamint) wants to merge 19 commits into
Conversation
Require the E2E matrix and lockfile registry guards to observe the inputs they validate before checking the input contents. This catches missing E2E matrix rows and lockfiles with no resolved registry entries instead of passing vacuously. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19177Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19177" |
There was a problem hiding this comment.
Pull request overview
Strengthens CI guards so missing tests, hang dumps, incomplete E2E matrices, and empty lockfiles fail reliably.
Changes:
- Validates nonzero TRX test counts and correct hang-dump filenames.
- Prevents retrying genuine test hangs.
- Adds lockfile and E2E matrix safeguards.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/run-tests.yml |
Strengthens test-result and hang-dump validation. |
.github/workflows/auto-rerun-transient-ci-failures.js |
Classifies hang detection as test failure. |
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs |
Adds workflow guard tests. |
tests/Infrastructure.Tests/WorkflowScripts/AutoRerunTransientCiFailuresTests.cs |
Tests hang retry classification. |
extension/scripts/validate-lockfile-registry.cjs |
Rejects lockfiles without resolved entries. |
extension/src/test/e2eLaunchProfile.test.ts |
Tests lockfile and E2E matrix completeness. |
Review details
Tip
Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (4)
.github/workflows/run-tests.yml:730
- This still cannot detect hangs from the repository's actual MTP invocation:
eng/Testing.props:41passes--hangdump-type noneto every generatedmtpBaseArgs, anddocs/ci/mtp-args-pipeline.md:56documents that this disables hang-dump file creation. The regression test only creates synthetic_hang.dmpfiles, so it does not show that CI produces anything this glob can match. Either enable dump creation or classify hangs from evidence that exists with the current configuration (for example, the persisted MTP timeout exit code), with a regression test covering that production path.
# MTP's default hang dump file name is "<process>_<pid>_hang.dmp"; use the suffix so
# crash dumps and unrelated .dmp files do not fail otherwise-successful runs.
$hangDumpFiles = Get-ChildItem -Path $testResultsDir -Filter *_hang.dmp -Recurse -ErrorAction SilentlyContinue
.github/workflows/run-tests.yml:685
validFileCountis incremented beforeCounters/@totalis validated, so a TRX with a missing or non-numeric count leaves$totalTestCountat zero and is accepted wheneverallowZeroTestsis true. The opt-out should permit only a successfully parsedtotal="0"; count the file as valid only after its total has been parsed.
if ($countersNode -and $countersNode.total) {
$testCount = [int]$countersNode.total
$totalTestCount += $testCount
.github/workflows/run-tests.yml:47
- The generated test matrix always includes an
uncollected:*backstop (eng/scripts/scan-test-partitions-from-source.ps1:123-124), which runs with--filter-not-trait "Partition=*"(eng/scripts/build-test-matrix.ps1:206-210) and can legitimately match zero tests;eng/Testing.props:41therefore already ignores MTP exit code 8. None of the reusable-workflow callers pass this new input, so those empty generated shards now fail despite the opt-out. Propagate an explicit matrix flag for the uncollected entry and pass it asallowZeroTestsfromtests.yml(and any generated-shard caller that needs it).
This issue also appears on line 728 of the same file.
# Only set this for generated shards that are expected to be empty. Normal CI should
# fail when a named shard produces .trx files that report zero tests.
allowZeroTests:
required: false
type: boolean
default: false
.github/workflows/run-tests.yml:660
- Because the configured
--hangdump-type noneproduces no dump file, a tolerated MTP timeout (exit code 3) with no TRX or recording still falls through to “Tests may not have run” and fails quarantine mode. Include the already-read MTP exit code in this classification (or enable dump creation) so the intended timeout path does not depend on an artifact CI suppresses.
# MTP's default hang dump file name is "<process>_<pid>_hang.dmp"; use the suffix so
# crash dumps and unrelated .dmp files stay out of this timeout-only classification.
$hasHangDumps = Get-ChildItem -Path "${{ github.workspace }}/testresults" -Filter *_hang.dmp -Recurse -ErrorAction SilentlyContinue
if ($hasRecordings -or $hasHangDumps) {
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (1)
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:334
Infrastructure.Testsalready providesTemporaryWorkspace(used byBuildTestMatrixTests), but this recreates scratch-directory allocation and cleanup manually. That bypasses the shared workspace cleanup/preservation behavior and the repository temp-directory abstraction. UseTemporaryWorkspace.Create(_output)and dispose it rather than maintaining this helper.
string scratchRoot = Path.Combine(RepoRoot.Path, "artifacts", "tmp", nameof(RunTestsWorkflowTests));
Directory.CreateDirectory(scratchRoot);
string scratchDirectory = Path.Combine(scratchRoot, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(scratchDirectory);
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Balanced
extension/scripts/validate-lockfile-registry.cjs allowed any resolved line that merely *contained* the internal feed string, so a hostile tarball URL could smuggle it into a path, hostname suffix, or URL fragment, or downgrade the scheme to plaintext http, and still pass. Parse each resolved URL with `new URL()` and require exact protocol (https:), exact hostname (pkgs.dev.azure.com), and a pathname prefix match. Any unparseable URL is rejected, not skipped. tests.yml duplicated the same vulnerable substring check inline (in both extension_tests_win and extension_bootstrap_linux) instead of calling the shared script, so it would have kept the bypass alive and let the two implementations drift apart even after the .cjs fix. Both steps now invoke the shared script, matching how extension-e2e-tests.yml already does it. Extend the existing spawnSync-based test harness in e2eLaunchProfile.test.ts with cases for the four bypass shapes (path injection, hostname suffix, http downgrade, fragment injection) and two genuine internal-feed URLs (plain + scoped package, with a sha512 fragment). Add a workflow-parsing test in RunTestsWorkflowTests.cs asserting every "Validate lockfile registries" step in tests.yml invokes the shared script rather than an inline substring check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (2)
.github/workflows/run-tests.yml:685
allowZeroTestscurrently treats a missing or unparseableCounters/@totalas an explicitly reported zero.validFileCountis incremented before this cast, and the catch only warns, so a TRX withtotal="invalid"(or noCounters) exits successfully when the opt-out is enabled. Track whether a test count was successfully parsed and fail if none was; the opt-out should cover a reportedtotal="0", not malformed result data.
if ($countersNode -and $countersNode.total) {
$testCount = [int]$countersNode.total
$totalTestCount += $testCount
.github/workflows/auto-rerun-transient-ci-failures.js:60
- The new
Verify test results existstep can now fail for a deterministic zero-test run, but it is absent fromtestExecutionFailureStepPatterns. Consequently, an unrelated feed signature anywhere in the job log still enables the broad network override and reruns that zero-test job—the same false-rerun path this change closes for hang-dump detection. Classify this step as a test-execution failure and add the corresponding regression case.
/^Check for hang dump files$/i,
- Files reviewed: 11/11 changed files
- Comments generated: 2
- Review effort level: Balanced
URL.hostname excludes the port, so comparing hostname accepted https://pkgs.dev.azure.com:444/... and let Yarn fetch from an unapproved endpoint on the right host. Compare URL.origin instead, which normalizes the default :443 away so the legitimate explicit-default form still passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 487b899d-d79d-4349-a84b-a026861bde7f
PR microsoft#19177 parameterized run-tests.yml's zero-test guard, but specialized-test-runner.yml still reads its opt-out from matrix.tests.allowZeroTests. That key is never emitted by the runsheet rows generated in eng/SpecializedTestRunsheetBuilderBase.targets (rows only contain label, project, os, command, properties, mtpBaseArgs), so the expression `matrix.tests.allowZeroTests || false` always evaluates to false. This re-vacuums the guard for the quarantined and outerloop test matrices, which is a regression: those matrices are built exclusively by SpecializedTestRunsheetBuilderBase.targets, and that file unconditionally appends /p:IgnoreZeroTestResult=true to every generated row's test command, since a specialized test class may only exist on some OSes. Pass allowZeroTests: true directly at the specialized-test-runner.yml call site so it mirrors the unconditional IgnoreZeroTestResult=true tolerance already baked into every row this workflow consumes. This does not widen the opt-out anywhere else: tests.yml's separate call sites still read matrix.allowZeroTests from build-test-matrix.ps1's distinct matrix, which explicitly sets it only for the generated shards that need it. Verified by importing the real, unmodified eng/SpecializedTestRunsheetBuilderBase.targets into a throwaway harness project and invoking its RunTests target directly: the generated runsheet row JSON has no allowZeroTests key at all, and its command line unconditionally includes /p:IgnoreZeroTestResult=true. Added two regression tests to RunTestsWorkflowTests.cs: - SpecializedTestRunnerWorkflowAlwaysOptsIntoAllowZeroTests asserts the call site passes allowZeroTests: true and does not read it from the matrix row. Fails against the pre-fix expression. - SpecializedTestRunsheetBuilderUnconditionallyIgnoresZeroTestResult asserts the /p:IgnoreZeroTestResult=true line in the targets file has no Condition, documenting the invariant the fix relies on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d1cf760-b1bc-46ba-a6d4-628354b00f2c
There was a problem hiding this comment.
Review details
Suppressed comments (2)
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:324
- This second PowerShell-backed theory also omits
RequiresTools(["pwsh"]), unlike the neighboring cases at lines 270-272 and 348-350. Add the tool requirement so unsupported environments skip consistently instead of failing to startpwsh.
[Theory]
[InlineData(3)]
[InlineData(7)]
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:297
- This theory launches
pwshbut lacks theRequiresTools(["pwsh"])guard used by every other PowerShell-backed test in this file (for example, lines 270-272). Without PowerShell installed, the tests intended to skip instead fail while the surrounding cases skip.
This issue also appears on line 322 of the same file.
[Theory]
[InlineData(3)]
[InlineData(7)]
- Files reviewed: 15/15 changed files
- Comments generated: 1
- Review effort level: Balanced
The exit-code classification only failed on an explicit list of fatal infrastructure codes (4, 5, 10, 11, 12). In ignoreTestFailures mode any other nonzero code fell through, so a run could still be reported green whenever a parseable .trx existed - including exit code 1 (unknown/unhandled error) and 9 (minimum execution policy violated), neither of which is a test outcome. Gate quarantine mode on the tolerated allow-list instead, so an unrecognized code fails closed. The fatal-infrastructure check is kept so normal CI keeps its existing behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d1cf760-b1bc-46ba-a6d4-628354b00f2c
|
Fixed in Quarantine mode now fails closed on any nonzero code outside the tolerated allow-list (2, 3, 7, 13), so an unrecognized code can no longer be silently tolerated. The fatal-infrastructure check is retained so normal CI keeps its existing behavior. Regression: |
There was a problem hiding this comment.
Review details
Suppressed comments (1)
.github/actions/enumerate-tests/action.yml:46
- This output-contract comment says every class entry tolerates zero tests, but
expand-test-matrix-github.ps1:111-115explicitly changesallowZeroTeststofalsefor Linux rows. Document that discovery-OS exception here; otherwise callers are told the opposite of the emitted matrix behavior.
# - allowZeroTests: (class type only) True. Class shards are generated mechanically from the
# discovered class names, so a class that is real on one OS can be legitimately empty on
# another once CI applies its environment trait filters (for example a class whose tests all
# require containers, filtered out on Windows). Discovering zero tests is therefore not a
# failure for these entries, unlike regular and collection shards.
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0d1cf760-b1bc-46ba-a6d4-628354b00f2c
There was a problem hiding this comment.
Review details
Suppressed comments (6)
.github/workflows/run-tests.yml:736
- This
Write-Erroris also inside the XML parsingtry. GitHub Actions sets$ErrorActionPreference = 'Stop', so a negative counter jumps to the catch and is ignored when quarantine mode has a tolerated exit code. Keep the diagnostic non-terminating so negative counters always reachexit 1.
if ($testCount -lt 0) {
Write-Error " $($trxFile.Name) has a negative ResultSummary/Counters/@total value '$testCount'."
exit 1
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:281
- This PowerShell-backed theory is missing
RequiresTools(["pwsh"]), so the test fails rather than skips whenpwshis unavailable.
[Theory]
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:307
- Add the same
pwshtool requirement as the surrounding process-based tests; otherwise this theory fails on hosts where PowerShell is not installed.
[Theory]
.github/workflows/run-tests.yml:732
- In GitHub Actions, the generated
pwshscript runs with$ErrorActionPreference = 'Stop', so thisWrite-Errorthrows into the surroundingcatchbeforeexit 1executes. For quarantine runs with exit code 2/3/7/13, that catch treats the error as an ignorable incomplete TRX, allowing an unparseable counter to pass even though this branch is intended to fail closed. Force this diagnostic to remain non-terminating so the explicit exit is reached.
This issue also appears on line 734 of the same file.
if (-not [int]::TryParse([string]$countersNode.total, [ref]$testCount)) {
Write-Error " $($trxFile.Name) has an unparseable ResultSummary/Counters/@total value '$($countersNode.total)'."
exit 1
tests/Infrastructure.Tests/WorkflowScripts/RunTestsWorkflowTests.cs:256
- This theory launches
pwshbut lacks theRequiresToolsguard used by the other PowerShell tests in this file. On environments without PowerShell, it will fail during process startup instead of being skipped.
This issue also appears in the following locations of the same file:
- line 281
- line 307
[Theory]
.github/actions/enumerate-tests/action.yml:46
- This output documentation says every class row has
allowZeroTests: true, butexpand-test-matrix-github.ps1explicitly changes the Linux discovery row tofalse. Document the per-OS behavior so consumers do not assume the zero-test opt-out applies to the discovery OS.
# - allowZeroTests: (class type only) True. Class shards are generated mechanically from the
# discovered class names, so a class that is real on one OS can be legitimately empty on
# another once CI applies its environment trait filters (for example a class whose tests all
# require containers, filtered out on Windows). Discovering zero tests is therefore not a
# failure for these entries, unlike regular and collection shards.
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3253974d-4f18-486f-863c-281a607656d4
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3253974d-4f18-486f-863c-281a607656d4
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Summary
Makes the .NET test-result guards fail when CI did not produce trustworthy test execution evidence.
Changes
*_hang.dmpfilenames and classifies result-validation/hang-dump failures as test execution failures for transient reruns.The extension E2E matrix and npm lockfile registry guards are intentionally not part of this PR.
Validation
Checklist