Files
scriptc/tests/harness/diagnostics.test.ts
Chris Tate 1f9badf573 Key harness scratch dirs by suite flavor so concurrent plain/san runs never share build dirs
- library-mode, library-contract (cache dir plus the tmpdir npm root), and library-asyncfree now carry a plain/san path segment: both flavors run the same ordinary builds, and the nightly's concurrent both-flavor recipe raced ar/ranlib in the shared dirs ("can't open file ... lib.lib.a"); the per-build -san tag stays, it distinguishes explicitly-sanitized builds within one suite
- the same flavor split lands on npm-static's fixed npm-static-workspace dir (the two flavors even build with different sanitize flags there), provenance's shared outDir, and diagnostics' $TMPDIR/scriptc-diag scratch (some entries emit backend artifacts before failing)
- dec-oracle publishes in the differential/llvm/linux/windows lanes are now atomic (write tmp, rename): the content-keyed path is shared across flavors and lanes, and a truncate-and-rewrite could expose a torn oracle to a concurrent reader
- every other suite already keys its build dirs by a content hash that includes the flavor, and the CAS build cache folds the sanitized flags into its key, so those stay as they are
- proof: three consecutive concurrent both-flavor library-mode runs from a wiped cache all pass 45/45 with zero ar/ranlib failures, and a concurrent both-flavor run of all four library suites passes 125/125 in each flavor
2026-07-24 06:08:45 -05:00

50 lines
2.3 KiB
TypeScript

/* Diagnostics quality is a tested artifact: every tests/diagnostics/*.ts
* program (and JS entries — CommonJS fence diagnostics live on .js files)
* must FAIL to compile, and the full rendered output (codes, spans, code
* frames, hints) is snapshotted.
*/
import { globSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { compile, renderAll } from "@scriptc/compiler";
const repoRoot = join(import.meta.dirname, "../..");
const diagDir = join(repoRoot, "tests/diagnostics");
const files = ["ts", "js", "mjs", "cjs"]
.flatMap((ext) => [
...globSync(join(diagDir, `*.${ext}`)),
...globSync(join(diagDir, `*/main.${ext}`)),
])
.sort();
describe(`diagnostics corpus (${files.length} programs)`, () => {
test.for(files.map((f) => [f.slice(diagDir.length + 1), f] as const))(
"%s",
async ([name, file]) => {
// Flavor-split scratch: some entries reach the backend and emit
// artifacts (.ll etc.) before failing, and the other flavor's
// concurrent suite writes the same names — never share the dir.
const outDir = join(tmpdir(), `scriptc-diag-${process.env["SCRIPTC_SAN"] === "1" ? "san" : "plain"}`);
// `// @dynamic` on the entry's FIRST line: compile with the island
// engine embedded — for diagnostics only --dynamic builds can reach
// (dynamic-import resolution failures, island-only fences).
const dynamic = /^\/\/ @dynamic\s*$/.test(
readFileSync(file, "utf8").split("\n", 1)[0] ?? "",
);
// Deliberately NO backend pin: every program here must fail before
// any backend runs (that is the suite's contract), so the default
// lane is irrelevant by construction — and riding it proves the
// fallback machinery never turns a diagnostic into a build.
const result = await compile(file, { outPath: join(outDir, "never"), outDir, dynamic });
if (result.ok) {
expect.unreachable(`${name} compiled successfully but must produce diagnostics`);
}
const rendered = renderAll(result.diagnostics, result.sourceTexts, { color: false })
// keep snapshots machine-independent
.replaceAll(diagDir + "/", "");
await expect(rendered).toMatchFileSnapshot(`__snapshots__/${name}.txt`);
},
);
});