A regression test that passes proves the code passes the test. It does not prove the test can detect the defect it is named for. Those are different claims, and only one of them is what a regression test is for.
A pin is only a pin if it fails against the defect. Revert only the fixing line(s), run the test, confirm it fails with the message you expect, restore, confirm it passes. Record the failure output in the change that introduces the test.
This is not extra rigour for hard cases. It is the cheapest available check on the one property a regression test must have, and it costs a minute. The alternative is a test whose failure mode is a false pass: the guard silently disappears and the green tick does not.
The procedure
- Write the test against the fixed code. Green.
- Revert only the fix. Not the test, not the signature, not a nearby refactor — the specific lines that change behaviour. If you cannot isolate them, that is itself a finding: the fix is entangled with something else.
- Run and read the message. It must be the failure you predicted, in the words you predicted. "It failed" is not enough — a test that fails for the wrong reason is still not a pin.
- Restore, re-run, confirm green.
- Paste the red output into the PR body. That output is the evidence; the green run is not.
Two things this also buys you, beyond confirming the pin:
- It rules out competing explanations, for free. A control holds everything else fixed by
construction. When
SyncedQueryChangeFeedStarvationTestwas reverted to the old change feed and reproduced the reported 30-second signature verbatim, that simultaneously eliminated "does the write publish on the watched path?" and "does the path matcher disagree for a children-scoped watcher?" — same publish, same matcher, same query, only the fan-out differed between red and green. See Change-Feed Isolation. - It tells you when a test is a guard rather than a pin. In one four-test suite only two went red under the control; the other two passed in both states, and that was the point — they existed to stop the fix from breaking a neighbouring behaviour. Say which is which. A suite where everything passes in both states pins nothing at all.
State plainly where you could not produce a difference. One control broke coalescing "the other way" — each track creating its own node — and both the old and new test shapes went red. The report said so, and named the remaining exposure as timing-dependent and therefore not demonstrable without rigging the clock. A negative control that comes out neutral is a result, not a failure to report.
Four tests that proved nothing
Each of these was written in good faith, passed, and was believed. What broke them is not carelessness — it is that the assertion measured something adjacent to the property.
1. The count was 1 either way
TrackActivity_ConcurrentSamePath_DoesNotRaceAlreadyExists fires five concurrent posts at one path
and asserts they coalesce into one node. It waited for the first node to surface on the
eventually-consistent query index, then asserted HaveCount(1).
The path is the storage key. The node count is 1 whether or not the race is coalesced — with
the coalescing fold removed, four of five creates throw Node already exists and their increments
are simply lost, and the count stays 1. The test could not detect the race it was named for, and the
blind spot is structural, not timing.
Demonstrated rather than argued: a temporary replica of the old shape ran side by side with the migrated test against the same deliberately broken handler, in the same run.
TEST FAILED: Expected value to be 5 ... but found 1 <- migrated test (RED)
Passed <- old PollForFirst shape (GREEN)
Failed: 1, Passed: 1
The assertion that actually pins the defect is AccessCount == 5 — every track folded its increment
onto the live record. The waiting had to change too: the wait now covers all five writes
(WhenSettled(path, writes)), because a wait that returns while writes are still in flight makes
even a correct assertion vacuous.
The lesson to carry: ask what the assertion's value would be if the defect were present. If the answer is "the same", the assertion is not measuring the property. Counting is the usual culprit, because a count is so often invariant under exactly the merging or deduplication you are trying to test.
2. Something else was doing the blocking
PixelRenderIsolationTests covers the pixel export's server-side-request-forgery surface: a headless
browser renders user-authored slide HTML inside the server's trust boundary, so it can reach
internal services the deck's author never could. There are two independent defences — a
Content-Security-Policy meta tag in the composed document, and Chromium's own process-level network
denial flags.
The first draft asserted the CSP with both layers armed. It passed. It would have passed with no CSP at all, because the process flags were doing the blocking.
"No request arrived" is a claim with many explanations, and most of them are not the one you want: the browser never had a route, the listener was misconfigured, the markup was wrong. Testing two layers at once hides a hole in either.
The rebuilt suite makes each test single-variable, and demands a leak before believing a block:
// ── Control: without the policy the slide MUST reach the listener. ──
probe.Reset();
await renderer.Render(WithoutPolicy(html)).Should().Emit();
var leaked = probe.Connections;
leaked.Should().BeGreaterThan(0,
"the control must demonstrate the vector is real — a slide CAN otherwise make the "
+ "server's browser open a connection of its choosing. At 0 the assertion below would "
+ "prove nothing, which is exactly how the first draft of this test fooled itself.");
// ── Protected: the composed document must reach nothing. ──
probe.Reset();
var pdf = await renderer.Render(html).Should().Emit();
probe.Connections.Should().Be(0, "…this is the SSRF surface…");
The_policy_alone_stops_a_slide_from_opening_a_connection runs with the process-level denial
neutralised, so the CSP is the only thing left that could block.
The_process_flags_stop_it_too_even_with_no_policy_at_all strips the CSP entirely. Two layers, two
tests, one variable each.
Note the third safeguard: the helper that strips the policy throws if the meta tag is ever renamed, rather than silently returning an identical document. A control that quietly stops being a control turns the whole suite green-and-meaningless — which is the same failure class one level up.
The lesson to carry: when an assertion is negative — nothing happened, no request arrived, nothing leaked — a live positive control is mandatory. Disable one layer at a time and require the control to fail.
3. It samples, so a green run is not evidence
MeshHubDisposalLeakTest walks GC roots looking for a MessageHub that survived disposal. It is an
excellent discovery probe and it found nine real leaks. It is not a pin, and a green run from it
is not evidence:
- It samples. A root that is live only for a bounded window — 1 s for a watcher re-establish, 100 ms for a log flush — is caught only if the probe's forced GC lands inside that window. Fire first, get collected, go green, with the defect fully present.
- It cannot attribute. It reports the first hub reachable from any non-stack root, which may be a different defect than the one you are chasing.
- It is inconclusive off Linux. ClrMD snapshot-attach throws on macOS, so a surviving hub skips.
The pin for a leak is a timing-free ownership test next to the code that owns the subscription:
after the owner is disposed, the handle holding the pending timer is disposed too — a property that
holds whether or not the timer has already fired, so the test can neither flake nor pass by accident.
A WeakReference probe would be a sampling test of a 100 ms window, i.e. the thing that pins nothing.
Subscription Ownership carries the full treatment, including
the negative-control table for the two live sites.
The carve-out, which proves the rule rather than weakening it (#3321). What makes a
WeakReference a sample is that its truth depends on when it is evaluated. Remove the when and
it stops being one. A hub's Dispose() returns while its teardown is still running, so the window a
naive probe samples is "did the shutdown happen to finish yet" — but that window has an explicit end
signal, DisposalCompleted. A probe that joins the signal and only then collects is asserting
something timing-free: after the owner's teardown is complete, nothing but a kept reference can hold
it. That is a property, not a snapshot, and StreamReleasesItsHubTest pins it.
So the test is not "is it a WeakReference?" but "does the assertion name the moment it becomes
true?" A probe with no such moment — a 100 ms log flush, a 1 s watcher re-establish — has none to
name and stays a discovery tool. The same page's own lesson applies unchanged, and the measured cost
of getting this wrong is in Writing Tests: without the join, that
assertion passed in a 485-test suite and failed alone on the same binary.
The lesson to carry: if the assertion's truth depends on when it is evaluated, it is a sample, not a proof. Sampling probes belong in discovery, never in the regression suite as the guard for a specific defect.
4. The control was served by the transport the test then destroyed
The first three measured something adjacent to the property. This one is worse, and it is the shape to learn: the control was guaranteed green by construction, and it was actively preventing the state the test went on to assert.
PodHubTransportTest.CrossSiloNack_ReachesASenderWhoseStreamSubscriptionIsGone erases a sender's
Orleans stream subscription and asserts that a router's NACK still reaches it — over the directed
pod-hub transport, which is the whole point. Before erasing, it proved the address was live:
// "Prove the pod-hub claim is LIVE before erasing anything — by a cross-silo delivery ARRIVING"
await WaitUntil(async () => {
SiloMeshHub(cluster, 1).Post(new PingRequest(), o => o.WithTarget(sender));
return inbox.Any(d => Describes(d, nameof(PingRequest)));
}, …);
That comment states the intent exactly. The code does not do it. RegisterStream establishes two
things on different clocks — the local route synchronously, and the cluster-wide pod-hub claim
asynchronously, on a capped backoff with no give-up. The probe asserts reachability, and during
the window that matters reachability is served by the stream — the transport erased three lines
later. So the probe was green whether or not the claim had landed.
And it fought the claim it appeared to prove. The probe posts from the other silo in a loop.
Each post whose directed call finds no claim mints a throw-away IPodHubGrain activation on that
silo — [PreferLocalPlacement] places an unactivated grain on the caller — and the owner's next
Attach lands on that activation, answers false, and restarts its backoff. The louder the control
ran, the longer the condition took to become true.
The failure was therefore intermittent and independent of the diff: two heads of one pull request differing only in a string-assertion test in another assembly disagreed on it, which is what got it filed as a platform race (#3298).
Measured. With one bounce forced and the claim's backoff pinned long, the probe goes green in
under a second while PodHubClaimSettled has demonstrably not completed — six runs, six times. That
experiment was deliberately not committed: its setup depends on an activation-lifetime ordering
that cannot be enforced, so as a permanent test it would have become the next flake. An experiment
that pins a cause and a test that guards it are different artifacts, and the first does not have to
become the second.
The cure is to wait on the condition itself — PodHubClaimSettled, the positive signal for "the claim
stopped attempting" — which a sibling test already did. See
Orleans Test Routing Pattern → "Reachability is not a
claim".
The lesson to carry: when a test isolates one mechanism in order to assert another, ask what serves the precondition probe while the isolation is not yet in place. If the answer is "the thing I am about to remove", the probe cannot fail, and its greenness is not evidence of anything. The generalisation is broader than tests: any control whose green is guaranteed by construction — a detector that still passes with the fix removed, a gate that skips when its input is missing, a watcher that renders "I cannot see" as "nothing is wrong" — is the same defect wearing a control's clothes.
What a good control looks like in a PR
Name the test, show the reverted state, show the message.
`CreateNodeAlwaysAnswersTest` (4 tests). With the fix: 4/4 pass in 411 ms.
With ONLY MeshExtensions.cs reverted:
Create_WhenAdapterDeclinesTheWrite_AnswersWithFailure [FAIL] [15 s]
Expected the observable to emit a value within 15s … but it did not.
The observable emitted nothing at all.
Create_WhenSaveCompletesWithoutEmitting_AnswersWithFailure [FAIL] [15 s]
… The observable emitted nothing at all.
Failed! - Failed: 2, Passed: 2
The other two pass in both states, which is the point: they are guards, not the pin.
A hang is the pre-fix behaviour there, so a timeout is what the assertion reports — and saying so explicitly is part of the evidence, because otherwise a reader cannot tell a real pin from a slow test.
When it is harder than reverting one line
- The fix is a deletion. Re-add the deleted lines; that is still "revert only the fix".
- The fix spans a predicate used in several places. Revert the predicate alone and expect several cases to go red together — five cases red against the previous predicate and green after is a perfectly good control, and is how the compile-settle predicate was pinned.
- The defect only appears under a scheduler race. Build both orderings as arms of one test,
so each arm is the other's control: post-then-observe must provably lose the reply, pre-register
must buffer and replay it. The fence is a real completion signal — two full round-trips, node
readability — never a fixed delay, so it cannot race.
UpsertInnerCreateObservationTestis the worked example. - You genuinely cannot make it deterministic. Say so, say what you did prove, and name the residual exposure. Do not upgrade "I could not reproduce it" into "it is fixed".
See also
- Writing Tests — the golden rules, the reactive assertion surface, and why a CI-only failure is never a flake.
- Reactive Test Assertions — the assertion API these controls are expressed in.
- Subscription Ownership — the leak case in full, with its control table.
- Silent Completion — the failure shape whose only symptom is a timeout, so its pin is a timeout.
- Orleans Test Routing Pattern — the pod-hub claim, and why a reachability probe cannot stand in for it.
- Change-Feed Isolation — a control that ruled out two competing hypotheses for free.