From e53f0c30ad734197c6864e6bb0ac1b546c3470df Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Sat, 13 Jun 2026 09:16:40 -0700 Subject: [PATCH] #54: make undo-dependent E2E resilient to environments where `undo` is broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `executeCommand("undo")` is non-functional in some headless `.vscode-test` instances (it does not restore the buffer — see #54's diagnosis), which false-fails every undo-dependent host E2E and turns `main`'s E2E red there even though the product code is fine. Add a RUNTIME preflight probe (`undoCapable.ts`): edit a scratch buffer, undo, and report whether the buffer was actually restored (memoized per run). The #38 undoMarks suite gates on it via `suiteSetup` — running normally where undo works (full coverage preserved, real regressions still caught) and skipping with a LOUD console warning where it doesn't (no false red, no silent loss — the skip is logged and shows as `pending`). This fixes the "main E2E red" symptom. The underlying headless-undo limitation is documented in #54; #40's undo-behavior coverage runs wherever undo works. Test-infra only — no product code changed. Verified: with undo broken locally the #38 suite skips (1 pending) and both E2E passes exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- test/e2e/suite/undoCapable.ts | 41 ++++++++++++++++++++++++++++++++ test/e2e/suite/undoMarks.test.ts | 9 +++++++ 2 files changed, 50 insertions(+) create mode 100644 test/e2e/suite/undoCapable.ts diff --git a/test/e2e/suite/undoCapable.ts b/test/e2e/suite/undoCapable.ts new file mode 100644 index 0000000..c61dabd --- /dev/null +++ b/test/e2e/suite/undoCapable.ts @@ -0,0 +1,41 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; + +/** + * #54: `vscode.commands.executeCommand("undo")` is non-functional in some headless + * `.vscode-test` instances (it does not restore the buffer), which false-fails + * every undo-dependent E2E (#38, #40) and makes `main` E2E red there. This is a + * RUNTIME probe: it performs a real edit-then-undo on a scratch buffer and reports + * whether undo actually restored it. Undo-dependent suites gate on it — running + * normally where undo works (real coverage), skipping with a loud warning where it + * doesn't (no false red, no silent loss — the skip is logged). Memoized per run. + */ +let cached: boolean | undefined; + +export async function undoWorks(): Promise { + if (cached !== undefined) return cached; + const abs = path.join(process.env.E2E_WORKSPACE!, "docs/.undo-probe.md"); + fs.mkdirSync(path.dirname(abs), { recursive: true }); + fs.writeFileSync(abs, "undo probe baseline\n", "utf8"); + const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(abs)); + await vscode.window.showTextDocument(doc); + await new Promise((r) => setTimeout(r, 250)); + const edit = new vscode.WorkspaceEdit(); + edit.insert(doc.uri, doc.positionAt(doc.getText().length), "PROBE-EDIT-MARKER"); + await vscode.workspace.applyEdit(edit); + await new Promise((r) => setTimeout(r, 250)); + if (!doc.getText().includes("PROBE-EDIT-MARKER")) { + cached = false; // even the edit didn't take — treat as not undo-capable + return cached; + } + await vscode.commands.executeCommand("undo"); + await new Promise((r) => setTimeout(r, 250)); + cached = !doc.getText().includes("PROBE-EDIT-MARKER"); // undo removed the marker → undo works + return cached; +} + +/** Loud, single-line reason logged when an undo suite skips (no silent loss — #54). */ +export const UNDO_SKIP_REASON = + "[E2E] SKIPPING undo-dependent suite — executeCommand('undo') is non-functional in this " + + "VS Code test instance (see vscode-cowriting-plugin#54). These tests run where undo works."; diff --git a/test/e2e/suite/undoMarks.test.ts b/test/e2e/suite/undoMarks.test.ts index ddeff88..7f6d6d0 100644 --- a/test/e2e/suite/undoMarks.test.ts +++ b/test/e2e/suite/undoMarks.test.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; import type { CowritingApi } from "../../../src/extension"; +import { undoWorks, UNDO_SKIP_REASON } from "./undoCapable"; const WS = process.env.E2E_WORKSPACE!; const settle = () => new Promise((r) => setTimeout(r, 400)); @@ -34,6 +35,14 @@ suite("F10 #38 — undo does not mis-attribute restored text (host E2E, no LLM)" const DOC_REL = "docs/undo38.md"; const BASE = "Alpha bravo charlie.\n"; + // #54: skip (loudly) where executeCommand("undo") is non-functional; run where it works. + suiteSetup(async function () { + if (!(await undoWorks())) { + console.warn(UNDO_SKIP_REASON); + this.skip(); + } + }); + test("undo of a deletion of baseline text leaves it unattributed (not human)", async () => { const { doc, key } = await freshDoc(DOC_REL, BASE); const api = await getApi();