Retry crashed test hosts, widen render tolerance, end unit++ cleanly - #1130
Open
johnml1135 wants to merge 4 commits into
Open
Retry crashed test hosts, widen render tolerance, end unit++ cleanly#1130johnml1135 wants to merge 4 commits into
johnml1135 wants to merge 4 commits into
Conversation
NUnit Tests 1 files ± 0 1 suites ±0 12m 20s ⏱️ -34s Results for commit 133eae5. ± Comparison against base commit 2b2c24a. This pull request removes 1 and adds 22 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1130 +/- ##
==========================================
+ Coverage 38.62% 38.81% +0.18%
==========================================
Files 1514 1517 +3
Lines 351047 351851 +804
Branches 40360 40510 +150
==========================================
+ Hits 135580 136559 +979
+ Misses 186267 186049 -218
- Partials 29200 29243 +43
🚀 New features to boost your workflow:
|
The verifier passed a snapshot when at most 4 pixels differed from the baseline. Font-smoothing drift between machines touches up to 50 pixels per scenario, each off by about 8 levels on one channel, so every RenderVerifyTests scenario failed on a box other than the one that captured the baselines. Pass only when the image size matches, fewer than 100 pixels differ, and the summed difference stays under 10 full-pixel equivalents, where a channel-saturated change on one pixel scores 1. Measured drift peaks at 50 pixels and magnitude 1.57; a shifted glyph touches thousands of pixels at a magnitude near 1 each, so it still fails. A size change fails regardless of tolerance so a layout regression cannot hide inside the pixel budget. The failure message now reports both measures, and the diff report records the magnitude limit. VerifyScenario drops an await of Task.CompletedTask that bought nothing under [Apartment(STA)]. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
TestViews reported [309-0-0] and retval=0, yet the runner returned -1. The Uniscribe shaping path loads the OS text-input stack, which connects to TextInputHost.exe over ALPC. The console harness has an STA apartment but no message pump, so after main returns those threads never finish tearing down and the process hangs. The runner kills it after its grace period, and that TerminateProcess is where the -1 came from. Call TerminateProcess with retval once every test and GlobalTeardown have run and stdout is flushed. Static destructors no longer run, which is a deliberate trade: everything meaningful has already completed. main.cc already carries repo-specific Windows patches (SuppressInteractiveCrashUi, TerminateOnSigAbrt), so this follows existing practice for the vendored harness. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A crashed test host aborts the vstest run, so every assembly after it never reports. The existing per-assembly fallback only fired on exit code -1, while a host crash returns 1, so about 1,600 tests vanished silently from a run that still ended with a summary. Detect the crash line in the vstest output and fall back to per-assembly runs. Each assembly, and a single-assembly run, is retried up to -MaxCrashAttempts (default 5) only when its host crashed; a reported test failure is an answer and is never retried. Match only "Test host process crashed", because vstest also prints the aborted line on Ctrl+C and CI cancellation. A run that passed only after a retry prints a [FLAKY] banner, writes TestResults/crash-retries.json, appends to the GitHub step summary, and exits with code 2 so a gate that reads only the exit code still sees it. CI uploads the report with the TRX artifacts. -Blame passes /Blame to vstest so a crash leaves a Sequence_*.xml naming the running test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
johnml1135
force-pushed
the
test-infra/crash-retry-render-tolerance
branch
from
September 9, 2026 20:36
3f1d253 to
a757e8a
Compare
papeh
approved these changes
Sep 11, 2026
papeh
left a comment
Contributor
There was a problem hiding this comment.
Slick changes! One suggested comment wording change.
| var savedArtifact = LoadSavedArtifact(expectedBitmap, verifiedPath, verifiedMetadataPath); | ||
| var diffSummary = CompareBitmaps(expectedBitmap, actualBitmap); | ||
| if (diffSummary.DifferentPixelCount <= MaxAllowedPixelDifferences) | ||
| // A size change is a layout regression, so it fails whatever the pixel tolerance |
Contributor
There was a problem hiding this comment.
I think "it fails regardless of the pixel tolerance" (as you have below) is a better wording
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three test-infrastructure fixes that make
test.ps1green and honest on a developer machine. A crashed managed test host is now retried and reported as flaky instead of silently dropping the assemblies after it. Render baselines tolerate cross-machine font-smoothing drift without admitting a moved glyph. The native unit++ harness exits cleanly instead of hanging and being killed with exit code -1.What a reviewer will ask: does the retry hide real failures? No. Only the vstest crash line triggers a retry; a run that reported a test failure is an answer and is never rerun. A run that passed only after a retry prints
[FLAKY], writesTestResults/crash-retries.json, appends to the GitHub step summary, and exits 2, so an exit-code gate still fails. The question worth your time is whether exit 2 is the right policy, and note it reaches more thanCI.yml:base-installer-cd.ymlandpatch-installer-cd.ymlalso runtest.ps1, so a recovered crash would fail an installer release build too. The alternative is a recovered crash staying green with the report as the only signal.Where to look
test.ps1Invoke-VsTestWithCrashRetry: one loop for both the single-assembly and per-assembly paths;Test-HostCrashedmatches onlyTest host process crashed, because vstest also prints the aborted line on Ctrl+C and CI cancellation.RenderSnapshotVerifier.IsWithinTolerance: size must match, fewer than 100 pixels may differ, and their summed magnitude must stay under 10 full-pixel equivalents. Measured drift peaks at 50 and 1.57.Lib/src/unit++/main.cc:TerminateProcessafterGlobalTeardown; static destructors no longer run..github/workflows/CI.yml:crash-retries.jsonrides with the TRX artifacts.Deliberately not here
-Blamerecords the culprit test, but a stack needsprocdumpon PATH for/Blame:CollectDumpon .NET Framework.Flakytest category or quarantine list. Nothing has shown a per-test flake in CI yet.Verification
Built with
build.ps1 -CommentHygiene -TokenHygiene.RootSiteTests99 passed, 1 skipped, with all 15VerifyScenariocases green on a box where 12 failed before. NativeTestGeneric[31-0-0] andTestViews[309-0-0], both exiting 0. Full suite once with all three fixes: 6067 tests, 6005 passed, 62 skipped, 0 failed, exit 0. The retry loop's crash branch was checked against a recorded crash transcript, not a live crash: none occurred during verification.Reading this a year from now -- start here
Main CI was green throughout. Every one of these three problems appeared only on a developer machine: render baselines captured on one box on 2026-09-03 drifted on the same box a week later, the unit++ hang needs the desktop text-input host that a CI runner does not start, and the managed host crash showed up in one full run out of three locally and never in CI. The branch makes local runs trustworthy; it does not claim to fix a CI failure.
Decisions, and why
crash-retries.jsonyet. The distinct code lets a future step treat "unstable" differently from "failed" without changing the script again.The active test run was abortedalso appears on cancellation. Withcancel-in-progress: truein CI, matching it would burn up to five retries on a run already being discarded.TerminateProcessin the vendored harness.main.ccalready carries repo-specific Windows patches, so patching it again follows precedent. The alternative, pumping messages or unloading the text-input stack, would be speculative work in a harness that has already printed its verdict.Paths not taken
[Retry]. NUnit's attribute retries assertion failures, not host crashes, which is the opposite of the policy wanted here.Marshal.ReleaseComObjectin the render harness. Proposed as a crash cause; rejected becauseSimpleRootSitedocuments removing that call as unnecessary and the productionGraphicsManagerdoes not make it either.-Blameandprocdump.Evidence
Measured render drift per scenario, with both limits forced to zero so every scenario emitted its diff:
Magnitude divided by touched pixels is about 8/255 everywhere: each differing pixel is off by roughly eight levels on all three channels, which is greyscale anti-aliasing drift. The verifier scores a pixel as
channelDelta / (3 * 255), so 24/765 is the same 8/255 ratio.TestGenericruns the sameOleInitialize/OleUninitializesequence and exits cleanly;TestViewsalone loadsTextShaping.dll,msctf.dllandtextinputframework.dlland leaves six threads parked aftermainreturns. The runner'sStop-Process -Forceis where -1 came from.Test host process crashedonly for a crashed host; the aborted line also appears on explicit cancellation (SuppressThe active test run was abortedmessage when a run is explicitly cancelled microsoft/vstest#2270).Preflight review details
Code Review Summary
Branch: test-infra/crash-retry-render-tolerance
Base: origin/main
Date: 2026-09-09
Review model: Claude Fable 5.1
Files changed: 5
Overview
Three local-only test-infrastructure defects surfaced while fixing the VwPattern
CI failure: render baselines drifted past a 4-pixel tolerance on a developer
box, native TestViews hung after
mainand was killed with exit code -1, and anintermittent managed test-host crash aborted full runs at 4429 of 6067 tests
with the per-assembly fallback never firing. Main CI was green throughout.
The branch retries crashed hosts with mandatory flaky reporting and exit code 2,
gates render baselines on size plus pixel count plus summed magnitude, and ends
the unit++ process explicitly after GlobalTeardown.
Contract/API Changes
test.ps1gains-MaxCrashAttempts(1-20, default 5) and-Blame, and anew exit code 2 meaning all tests passed but a host crashed and was retried.
RenderSnapshotVerifiertolerance changes from "at most 4 differing pixels"to "size matches, fewer than 100 differing pixels, summed magnitude under 10".
RenderSnapshotComparisonReportandRenderPixelDiffSummarygain magnitudefields.
main.Findings
Critical - Must address before merge
None.
Important - Should address before merge
exit code (fixed during review: exit code 2, GitHub step summary entry, and
crash-retries.json uploaded with the TRX artifacts)
-TestProjectrun got noretry (fixed during review: one
Invoke-VsTestWithCrashRetryloop servesboth paths)
superseded run would burn five retries (fixed during review:
Test-HostCrashedmatches only the crash line)Minor - Consider
extracted to
Test-HostCrashed)measured drift and glyph-shift magnitude noted at the constants)
TerminateProcesstrade-off not stated (fixed during review:comment names the skipped static destructors)
colour floor excludes anti-aliasing drift before counting. Industry tools
use a ratio plus a colour threshold. Deferred; noted in the PR body.
-Blamenames the culprit test but astack needs procdump for
/Blame:CollectDumpon .NET Framework. Deferred.Required Validation / Evidence
build.ps1 -CommentHygiene -TokenHygiene: clean.RootSiteTestssingle-assembly run through the new retry loop: 99 passed,1 skipped; all 15
VerifyScenariocases green where 12 failed before.TestGeneric[31-0-0] andTestViews[309-0-0], both exit 0.recorded crash output from an earlier aborted run through the predicate.
test.ps1 -Coveragethrough the new loop.Positive Observations
retrying a reported failure is how a suite starts lying.
main.ccalready carries repo-specific Windows patches, so this follows thefile's precedent.
Interview Notes
< 100 touched AND size mismatch failure").
"The worst is to crash and say 'it's fine'". Exit code 2 follows from that.
unattended soak hung the workstation, so reproduction is deferred to CI.
RenderVerifyTests.VerifyScenariowas suspectelimination for the host crash, kept because the await bought nothing.
In-Review Quality Check
powershell-compatclean on 5.1 and 7.0.Suggested Review Focus
case 50 and 1.57 and that ~10 fully changed pixels would pass?
TerminateProcessskipping static destructors in the unit++ harness.🤖 Generated with Claude Code
This change is