bench(hashline): fix accept regime measuring 3-way-merge success instead of replay path
Anchor configs placed rewrittenLine at floor(size/2) while anchors sat 8-1875 lines away. With Diff.structuredPatch using context=3, no hunk's context overlapped the rewrite, so applyEditsToSnapshot's 3-way merge succeeded on the first try and replaySessionChainOnCurrent (the path the bench claims to pin) was never invoked for the 6 accept cases. The sanity probe only checked probe !== null, which both branches satisfy, so the misalignment was silent. Move rewrittenLine to anchors[0]-1 for accept cases so the first hunk's context actually spans the rewrite, forcing applyPatch failure and replay invocation. Strengthen the sanity check to require RECOVERY_SESSION_REPLAY_WARNING (only emitted past verifyAnchorContent) on every accept case so a future regression fails loudly instead of silently measuring the wrong path.
This commit is contained in:
@@ -22,7 +22,13 @@
|
||||
* Sizes (50/500/5000 lines) × edit batch (1/8 anchors) so the O(N) split cost
|
||||
* in `verifyAnchorContent` and the O(K) anchor walk both surface.
|
||||
*/
|
||||
import { computeFileHash, InMemorySnapshotStore, parsePatch, Recovery } from "../src";
|
||||
import {
|
||||
computeFileHash,
|
||||
InMemorySnapshotStore,
|
||||
parsePatch,
|
||||
RECOVERY_SESSION_REPLAY_WARNING,
|
||||
Recovery,
|
||||
} from "../src";
|
||||
|
||||
const ITERATIONS = Number(Bun.env.HASHLINE_BENCH_ITERATIONS ?? "5000");
|
||||
const PATH = "/tmp/__hashline-recovery-bench__.ts";
|
||||
@@ -79,9 +85,15 @@ for (const size of [50, 500, 5000] as const) {
|
||||
Math.max(1, Math.floor(size / 3)),
|
||||
Math.max(2, Math.floor((size * 2) / 3)),
|
||||
];
|
||||
// Accept regime: the rewrite is the immediate neighbour of the first
|
||||
// anchor (≤3 lines), so the patch hunk's context spans the rewrite and
|
||||
// the 3-way merge fails atomically — forcing the replay path. Reject
|
||||
// regime: the rewrite IS the anchor, so verifyAnchorContent refuses.
|
||||
const acceptRewrite = sparse[0] - 1;
|
||||
const acceptRewriteDense = dense[0] - 1;
|
||||
cases.push(
|
||||
{ name: `accept ${size}L ×1 anchor`, lines: size, anchors: sparse, rewrittenLine: rewritten },
|
||||
{ name: `accept ${size}L ×8 anchors`, lines: size, anchors: dense, rewrittenLine: rewritten },
|
||||
{ name: `accept ${size}L ×1 anchor`, lines: size, anchors: sparse, rewrittenLine: acceptRewrite },
|
||||
{ name: `accept ${size}L ×8 anchors`, lines: size, anchors: dense, rewrittenLine: acceptRewriteDense },
|
||||
{ name: `reject ${size}L ×1 anchor`, lines: size, anchors: [rewritten], rewrittenLine: rewritten },
|
||||
);
|
||||
}
|
||||
@@ -113,13 +125,21 @@ for (const c of cases) {
|
||||
const args = { path: PATH, currentText: v1Text, fileHash: h0, edits };
|
||||
|
||||
// Sanity: every iteration must hit the expected branch. Otherwise the
|
||||
// numbers measure the wrong path.
|
||||
// numbers measure the wrong path. Accept cases additionally must surface
|
||||
// RECOVERY_SESSION_REPLAY_WARNING — its only emitter is the replay
|
||||
// fallback past verifyAnchorContent, so its absence proves we never
|
||||
// reached the gate this bench is supposed to pin.
|
||||
const probe = recovery.tryRecover(args);
|
||||
if (isReject) {
|
||||
if (probe !== null) throw new Error(`expected null for ${c.name}, got recovery`);
|
||||
expectedNullVerified++;
|
||||
} else {
|
||||
if (probe === null) throw new Error(`expected recovery for ${c.name}, got null`);
|
||||
if (!probe.warnings.includes(RECOVERY_SESSION_REPLAY_WARNING)) {
|
||||
throw new Error(
|
||||
`expected ${c.name} to surface RECOVERY_SESSION_REPLAY_WARNING (the only signal that the replay path executed); got warnings=${JSON.stringify(probe.warnings)}`,
|
||||
);
|
||||
}
|
||||
expectedNonNullVerified++;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user