fix: verify-references installs TS deps; sync-check covers expansion tests; template gitignore and passWithNoTests
verify-references ran a bare `npx vitest run` in the staged reference, which fetched an unrelated vitest that could not load the reference config. It now installs the reference's devDependencies first. sync-check also checks each module's expansion/tests against ### headings in expansion/README.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ffa99785c3
commit
1453a00b9c
@@ -244,10 +244,10 @@ cp -r workspace-template/ts ~/my-engine
|
||||
cd ~/my-engine
|
||||
git init && git add -A && git commit -m "start from template"
|
||||
npm install
|
||||
npm test -- --passWithNoTests
|
||||
npm test
|
||||
```
|
||||
|
||||
Copy the whole directory with `cp -r` so the hidden `.course.json` file (which records your track) comes along. With no tests yet, `cargo test` reports 0 tests, and `npm test` needs `--passWithNoTests` to exit cleanly.
|
||||
Copy the whole directory with `cp -r` so the hidden `.course.json` file (which records your track) comes along. With no tests yet, both `cargo test` and `npm test` report 0 tests and exit cleanly.
|
||||
|
||||
**Read the template's README.** Your engine goes in `src/engine/`, your cards in `src/content/`, and `src/harness` is the thin adapter the course's tests call.
|
||||
|
||||
|
||||
+3
-3
@@ -39,9 +39,9 @@ export function testNames(path, lang) {
|
||||
return [...names].sort();
|
||||
}
|
||||
|
||||
/** `### test_…` headings in a module's scenarios.md. */
|
||||
export function scenarioNames(path) {
|
||||
const f = join(path, "scenarios.md");
|
||||
/** `### test_…` headings in a module's scenarios.md (or another file in `path`). */
|
||||
export function scenarioNames(path, file = "scenarios.md") {
|
||||
const f = join(path, file);
|
||||
if (!existsSync(f)) return [];
|
||||
const names = new Set();
|
||||
for (const m of readFileSync(f, "utf8").matchAll(/^### (test_\w+)/gm)) names.add(m[1]);
|
||||
|
||||
+15
-8
@@ -1,24 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
// Verifies Rust tests, TS tests and scenarios.md list identical test names per module.
|
||||
// A module's Expansion Drop (expansion/tests/{rust,ts}, scenarios as `### test_…`
|
||||
// headings in expansion/README.md) is checked the same way, as a separate set.
|
||||
import { join } from "node:path";
|
||||
import { moduleDirs, testNames, scenarioNames } from "./lib.mjs";
|
||||
|
||||
const problems = [];
|
||||
for (const mod of moduleDirs()) {
|
||||
function check(label, path, scenarioFile) {
|
||||
const sets = {
|
||||
rust: testNames(mod.path, "rust"),
|
||||
ts: testNames(mod.path, "ts"),
|
||||
scenarios: scenarioNames(mod.path),
|
||||
rust: testNames(path, "rust"),
|
||||
ts: testNames(path, "ts"),
|
||||
scenarios: scenarioNames(path, scenarioFile),
|
||||
};
|
||||
if (Object.values(sets).every((s) => s.length === 0)) continue;
|
||||
if (Object.values(sets).every((s) => s.length === 0)) return;
|
||||
const all = new Set(Object.values(sets).flat());
|
||||
for (const name of [...all].sort()) {
|
||||
for (const [where, names] of Object.entries(sets)) {
|
||||
if (!names.includes(name)) {
|
||||
problems.push(`module ${mod.num}-${mod.slug}: ${name} missing from ${where}`);
|
||||
}
|
||||
if (!names.includes(name)) problems.push(`${label}: ${name} missing from ${where}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const mod of moduleDirs()) {
|
||||
const label = `module ${mod.num}-${mod.slug}`;
|
||||
check(label, mod.path, "scenarios.md");
|
||||
check(`${label} expansion`, join(mod.path, "expansion"), "README.md");
|
||||
}
|
||||
if (problems.length) {
|
||||
console.error("sync-check: mismatches\n" + problems.map((p) => " " + p).join("\n"));
|
||||
process.exit(1);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Expansion
|
||||
@@ -0,0 +1,2 @@
|
||||
#[test]
|
||||
fn test_01x_c() {}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Expansion
|
||||
|
||||
### test_01x_a
|
||||
@@ -0,0 +1,2 @@
|
||||
#[test]
|
||||
fn test_01x_a() {}
|
||||
@@ -0,0 +1,2 @@
|
||||
import { test } from "vitest";
|
||||
test("test_01x_a", () => {});
|
||||
@@ -14,3 +14,9 @@ test("sync_passes_consistent_course", () => {
|
||||
const r = run("sync-check.mjs", [], { COURSE_ROOT: join(fixtures, "course") });
|
||||
assert.equal(r.code, 0, r.out);
|
||||
});
|
||||
|
||||
test("sync_checks_expansion_tests_against_readme", () => {
|
||||
const r = run("sync-check.mjs", [], { COURSE_ROOT: join(fixtures, "course-bad") });
|
||||
assert.match(r.out, /01-a expansion: test_01x_c missing from ts/);
|
||||
assert.match(r.out, /01-a expansion: test_01x_c missing from scenarios/);
|
||||
});
|
||||
|
||||
@@ -45,3 +45,13 @@ test("verify_stage_copies_all_earlier_module_tests", async () => {
|
||||
for (const f of ["m01.rs", "m02.rs", "m03.rs"]) assert.ok(existsSync(join(dir, "tests", f)));
|
||||
assert.ok(existsSync(join(dir, "Cargo.toml")));
|
||||
});
|
||||
|
||||
test("verify_ts_default_installs_dependencies_first", async () => {
|
||||
// A staged reference has no node_modules; a bare `npx vitest` fetches the
|
||||
// latest vitest, which cannot load the reference's vitest.config.ts.
|
||||
const { commandFor } = await import("../verify-references.mjs");
|
||||
const cmd = commandFor("ts", {});
|
||||
assert.match(cmd, /^npm (ci|install)\b.*&& npx vitest run$/);
|
||||
assert.equal(commandFor("ts", { VERIFY_CMD_TS: "true" }), "true");
|
||||
assert.equal(commandFor("rust", {}), "cargo test");
|
||||
});
|
||||
|
||||
@@ -6,11 +6,17 @@ import { tmpdir } from "node:os";
|
||||
import { basename, join } from "node:path";
|
||||
import { moduleDirs, testFiles, fail } from "./lib.mjs";
|
||||
|
||||
const COMMANDS = {
|
||||
rust: () => process.env.VERIFY_CMD_RUST || "cargo test",
|
||||
ts: () => process.env.VERIFY_CMD_TS || "npx vitest run",
|
||||
// A staged reference has no node_modules, so TS installs the reference's own
|
||||
// pinned devDependencies first (a bare `npx vitest` would fetch an unrelated version).
|
||||
const DEFAULTS = {
|
||||
rust: "cargo test",
|
||||
ts: "npm install --no-audit --no-fund --loglevel=error && npx vitest run",
|
||||
};
|
||||
|
||||
export function commandFor(lang, env = process.env) {
|
||||
return env[`VERIFY_CMD_${lang.toUpperCase()}`] || DEFAULTS[lang];
|
||||
}
|
||||
|
||||
export function planVerification(mods, upTo) {
|
||||
return mods
|
||||
.filter((m) => Number(m.num) >= 1 && Number(m.num) <= upTo)
|
||||
@@ -41,7 +47,7 @@ if (import.meta.main) {
|
||||
for (const p of plans) {
|
||||
for (const lang of p.langs) {
|
||||
const dir = stage(p, lang);
|
||||
const r = spawnSync(COMMANDS[lang](), { cwd: dir, shell: true, encoding: "utf8" });
|
||||
const r = spawnSync(commandFor(lang), { cwd: dir, shell: true, encoding: "utf8" });
|
||||
const ok = r.status === 0;
|
||||
failed ||= !ok;
|
||||
console.log(`module ${p.num}-${p.slug} [${lang}]: ${ok ? "ok" : "FAIL"}`);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
target/
|
||||
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: { include: ["tests/**/*.test.ts"] },
|
||||
test: { include: ["tests/**/*.test.ts"], passWithNoTests: true },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user