inspector/scripts/verify-typecheck-coverage.main.test.mjs
olaservo 7164806be3 Merge origin/v2/main: move the #1939 npx fix into the shared tsc-program lib
Upstream #1965 moved the tsc --listFilesOnly machinery into
scripts/lib/tsc-program.mjs (shared with the new verify:dep-lockstep),
still spawning `npx` — which is ENOENT on Windows. Resolve the conflict
by taking upstream's factoring and porting the #1939 fix into the lib:
rawProjectListing and verify-typecheck-coverage's --showConfig pass now
spawn process.execPath with the cached, hard-failing tscEntry resolver
(exported from tsc-program.mjs). Fixture copies in both main tests pick
up the new lib import graph. verify:dep-lockstep now runs on Windows
too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:07:50 -07:00

106 lines
4.5 KiB
JavaScript

// Regression test for #1939's "doubly silent" failure mode: when the tsc entry
// cannot be resolved (on Windows the old `execFileSync("npx", …)` threw ENOENT;
// today, a missing install), the guard must hard-fail with an actionable
// "cannot measure" error — NOT swallow it, echo "(no diagnostic captured)" per
// project, and then report every tracked source file in the repo as getting no
// tsc pass, which is what shipped before and sent Windows contributors chasing
// a 900-file coverage regression that wasn't real.
//
// The fixture is a throwaway repo with one enrolled client whose `typecheck`
// names a project, but with NO node_modules anywhere up the temp tree — so the
// guard reaches the tsc-entry resolution and it fails. Mirrors the
// `verify-format-coverage.main.test.mjs` / `verify-dep-lockstep.main.test.mjs`
// pattern. Run via `npm run test:scripts`.
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawnSync } from "node:child_process";
import {
cpSync,
mkdirSync,
mkdtempSync,
realpathSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
test("unresolvable tsc is a hard 'cannot measure' error, not an empty file set", () => {
// realpath'd because the script only executes when `import.meta.url` matches
// `process.argv[1]`, and macOS `tmpdir()` is a symlink — see the note in
// `verify-dep-lockstep.main.test.mjs`.
const dir = realpathSync(mkdtempSync(path.join(tmpdir(), "typecheck-cov-")));
try {
// Root manifest: the full guard cycle is wired so phase 1 gets as far as
// measuring the client (an unwired client is `continue`d past, and the
// resolution would never be reached).
writeFileSync(
path.join(dir, "package.json"),
JSON.stringify({
name: "fixture",
scripts: {
validate:
"npm run verify:format-coverage && npm run verify:typecheck-coverage && npm run verify:dep-lockstep && npm run test:scripts && npm --prefix clients/cli run validate",
"verify:format-coverage": "node scripts/verify-format-coverage.mjs",
"verify:typecheck-coverage":
"node scripts/verify-typecheck-coverage.mjs",
"verify:dep-lockstep": "node scripts/verify-dep-lockstep.mjs",
"test:scripts": 'node --test "scripts/**/*.test.mjs"',
},
}),
);
// One enrolled client with a `typecheck` reachable from `validate`, a
// project for it to name, and a tracked source file — so if the hard error
// ever regresses to the old behavior, the "get no `tsc` pass" report the
// second assertion forbids would actually have a file to list.
mkdirSync(path.join(dir, "clients", "cli", "src"), { recursive: true });
writeFileSync(
path.join(dir, "clients", "cli", "package.json"),
JSON.stringify({
name: "fixture-cli",
scripts: {
validate: "npm run typecheck",
typecheck: "tsc --noEmit -p tsconfig.json",
},
}),
);
writeFileSync(
path.join(dir, "clients", "cli", "tsconfig.json"),
JSON.stringify({ include: ["src"] }),
);
writeFileSync(
path.join(dir, "clients", "cli", "src", "index.ts"),
"export const x = 1;\n",
);
mkdirSync(path.join(dir, "scripts", "lib"), { recursive: true });
for (const rel of [
"verify-typecheck-coverage.mjs",
path.join("lib", "npm-scripts.mjs"),
path.join("lib", "resolve-node-bin.mjs"),
path.join("lib", "tsc-program.mjs"),
])
cpSync(path.join(scriptsDir, rel), path.join(dir, "scripts", rel));
execFileSync("git", ["init", "-q"], { cwd: dir });
execFileSync("git", ["add", "-A"], { cwd: dir });
const r = spawnSync(
process.execPath,
[path.join(dir, "scripts", "verify-typecheck-coverage.mjs")],
{ cwd: dir, encoding: "utf8" },
);
const out = `${r.stdout}${r.stderr}`;
assert.equal(r.status, 1, out);
assert.match(out, /cannot resolve `typescript` from clients\/cli/, out);
assert.match(out, /npm install/, out);
// The pre-#1939 failure shape: per-project "(no diagnostic captured)"
// warnings followed by every tracked file reported uncovered.
assert.doesNotMatch(out, /get no `tsc` pass/, out);
assert.doesNotMatch(out, /no diagnostic captured/, out);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});