// What a landing checks and runs (src/landing.mjs, row 137, #1631): the // root-file rule, the mosaic-stack check, the gate's environment, its steps // and its runs, and the CLI land-work.sh calls. The gates here run in fake // trees with small packages and scripts; none runs this repository's suites. import { test } from "node:test"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { gitEnv } from "../src/checkout.mjs"; import { gateEnv, gateSteps, isMosaicStack, MOSAIC_ROOT_COMMIT, rootHits, runGate } from "../src/landing.mjs"; const REPO = fileURLToPath(new URL("../../..", import.meta.url)); const CLI = join(REPO, "packages", "seat", "src", "landing.mjs"); function tmp(t) { const dir = mkdtempSync(join(tmpdir(), "mosaic-landing-")); t.after(() => rmSync(dir, { recursive: true, force: true })); return dir; } function git(cwd, ...args) { const r = spawnSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.invalid", ...args], { cwd, encoding: "utf8", env: gitEnv() }); assert.equal(r.status, 0, `git ${args.join(" ")}: ${r.stderr}`); return r.stdout.trim(); } function write(path, text) { mkdirSync(join(path, ".."), { recursive: true }); writeFileSync(path, text); } // A test file that passes, or fails when `pass` is false. const nodeTest = (pass) => `import { test } from "node:test";\nimport assert from "node:assert/strict";\ntest("t", () => assert.ok(${pass}));\n`; // A tree with: packages a and c with tests, b without, queue (skipped); // scripts test-one.sh and test-two.sh, and other.sh, which isn't a step. function tree(t, { two = "echo two\n" } = {}) { const w = tmp(t); write(join(w, "packages", "c", "tests", "c.test.mjs"), nodeTest(true)); write(join(w, "packages", "a", "tests", "a.test.mjs"), nodeTest(true)); write(join(w, "packages", "b", "src", "b.mjs"), "export {};\n"); write(join(w, "packages", "queue", "tests", "q.test.mjs"), nodeTest(false)); write(join(w, "scripts", "test-two.sh"), two); write(join(w, "scripts", "test-one.sh"), 'printf "%s\\n" "${GATE_SEEN-unset}" > "$PWD/one.env"\n'); write(join(w, "scripts", "other.sh"), "exit 9\n"); const logs = join(w, "logs"); mkdirSync(logs); return { w, logs }; } // The test runner's own context would turn an inner `node --test` into its child. const env = (extra = {}) => ({ ...gateEnv(), ...extra }); test("rootHits: a top-level file, a new top-level directory, src/ and v1/ are the root; paths under known directories aren't", () => { const top = ["docs", "packages", "scripts", "src", "v1", "README.md"]; assert.deepEqual(rootHits(["packages/a/x.mjs", "docs/y.md", "NEW.md", "newdir/z", "src/a", "v1/b", "README.md"], top), ["NEW.md", "newdir/z", "src/a", "v1/b", "README.md"]); assert.deepEqual(rootHits([], top), []); }); test("gateEnv keeps the named variables that are strings and drops everything else", () => { const kept = gateEnv({ PATH: "/bin", HOME: "/h", TMPDIR: "/t", DOCKER_HOST: "unix:///d", MOSAIC_CONFIG: "/c", ANTHROPIC_API_KEY: "k", NODE_TEST_CONTEXT: "child-v8", TZ: undefined }); assert.deepEqual(kept, { PATH: "/bin", HOME: "/h", TMPDIR: "/t", DOCKER_HOST: "unix:///d" }); }); test("isMosaicStack: a checkout whose HEAD descends from the root commit and has node_modules; nothing else", async (t) => { const objects = join(git(REPO, "rev-parse", "--path-format=absolute", "--git-common-dir"), "objects"); const dir = tmp(t); const ours = join(dir, "ours"); git(dir, "init", "-q", ours); // Borrow this repository's objects rather than copy its history. writeFileSync(join(ours, ".git", "objects", "info", "alternates"), `${objects}\n`); const rootTree = git(ours, "rev-parse", `${MOSAIC_ROOT_COMMIT}^{tree}`); const child = git(ours, "commit-tree", rootTree, "-p", MOSAIC_ROOT_COMMIT, "-m", "child"); git(ours, "update-ref", "refs/heads/main", child); git(ours, "symbolic-ref", "HEAD", "refs/heads/main"); assert.equal(await isMosaicStack(ours), false, "no node_modules"); writeFileSync(join(ours, "node_modules"), ""); assert.equal(await isMosaicStack(ours), false, "node_modules is a file"); rmSync(join(ours, "node_modules")); mkdirSync(join(ours, "node_modules")); assert.equal(await isMosaicStack(ours), true); const other = join(dir, "other"); git(dir, "init", "-q", other); writeFileSync(join(other, "README"), "x\n"); git(other, "add", "README"); git(other, "commit", "-q", "-m", "x"); mkdirSync(join(other, "node_modules")); assert.equal(await isMosaicStack(other), false, "an unrelated history"); const plain = join(dir, "plain"); mkdirSync(join(plain, "node_modules"), { recursive: true }); assert.equal(await isMosaicStack(plain), false, "not a repository"); assert.equal(await isMosaicStack(join(dir, "missing")), false); }); test("gateSteps: each package with tests but queue in name order, then each scripts/test-*.sh", (t) => { const { w } = tree(t); assert.deepEqual(gateSteps(w), [ { name: "pkg-a", dir: join("packages", "a"), command: "node --test tests/*.test.mjs" }, { name: "pkg-c", dir: join("packages", "c"), command: "node --test tests/*.test.mjs" }, { name: "script-one", dir: ".", command: "bash scripts/test-one.sh" }, { name: "script-two", dir: ".", command: "bash scripts/test-two.sh" }, ]); assert.deepEqual(gateSteps(tmp(t)), []); }); test("runGate: every step passes, each with its own log and a summary line, under the environment it was given", async (t) => { const { w, logs } = tree(t); const lines = []; const r = await runGate({ worktree: w, logs, env: env({ GATE_SEEN: "given" }), report: (l) => lines.push(l) }); assert.deepEqual(r, { ok: true }); assert.deepEqual(lines.map((l) => l.split(" ")[0]), ["pkg-a", "pkg-c", "script-one", "script-two", "GATE_PASSED"]); assert.match(lines[0], /^pkg-a rc=0 ℹ pass 1 ℹ fail 0 $/); assert.equal(lines[2], "script-one rc=0 "); assert.equal(readFileSync(join(logs, "gate.log"), "utf8"), lines.map((l) => `${l}\n`).join("")); for (const s of ["pkg-a", "pkg-c", "script-one", "script-two"]) assert.ok(existsSync(join(logs, `${s}.log`)), s); assert.equal(readFileSync(join(logs, "script-two.log"), "utf8"), "two\n"); assert.equal(readFileSync(join(w, "one.env"), "utf8"), "given\n"); }); test("runGate: the first failing step stops the gate and names its log", async (t) => { const { w, logs } = tree(t, { two: "echo broke\nexit 3\n" }); // test-z.sh sorts after test-two.sh, so only a stop keeps it from running. write(join(w, "scripts", "test-z.sh"), "touch ran-z\n"); const r = await runGate({ worktree: w, logs, env: env() }); assert.deepEqual(r, { ok: false, step: "script-two", log: join(logs, "script-two.log") }); assert.match(readFileSync(join(logs, "gate.log"), "utf8"), /script-two rc=3 \n$/); assert.equal(existsSync(join(w, "ran-z")), false); // A failing node suite fails its package's step. const f = tree(t); write(join(f.w, "packages", "a", "tests", "a.test.mjs"), nodeTest(false)); const p = await runGate({ worktree: f.w, logs: f.logs, env: env() }); assert.deepEqual(p, { ok: false, step: "pkg-a", log: join(f.logs, "pkg-a.log") }); assert.match(readFileSync(join(f.logs, "gate.log"), "utf8"), /^pkg-a rc=1 ℹ pass 0 ℹ fail 1 \n$/); }); test("runGate: an abort stops the running step and everything in its group, and runs nothing after it", async (t) => { const { w, logs } = tree(t, { two: 'sleep 300 &\necho $! > "$PWD/sleep.pid"\nwait\n' }); write(join(w, "scripts", "test-z.sh"), "touch ran-z\n"); const ac = new AbortController(); const pid = join(w, "sleep.pid"); const gate = runGate({ worktree: w, logs, env: env(), signal: ac.signal }); while (!existsSync(pid) || !readFileSync(pid, "utf8").trim()) await new Promise((r) => setTimeout(r, 20)); ac.abort(); assert.deepEqual(await gate, { ok: false, aborted: true }); // The orphaned sleep may stay a zombie for a moment before init reaps it. const sleeper = Number(readFileSync(pid, "utf8")); const gone = () => { try { return readFileSync(`/proc/${sleeper}/stat`, "utf8").split(") ")[1][0] === "Z"; } catch { return true; } }; const until = Date.now() + 5000; while (!gone() && Date.now() < until) await new Promise((r) => setTimeout(r, 20)); assert.ok(gone(), `sleep ${sleeper} still runs`); assert.equal(existsSync(join(w, "ran-z")), false); assert.doesNotMatch(readFileSync(join(logs, "gate.log"), "utf8"), /script-two|GATE_PASSED/); // A signal that has already aborted runs nothing. const f = tree(t); const done = new AbortController(); done.abort(); assert.deepEqual(await runGate({ worktree: f.w, logs: f.logs, env: env(), signal: done.signal }), { ok: false, aborted: true }); assert.equal(existsSync(join(f.logs, "gate.log")), false); }); // The CLI land-work.sh calls. function cli(...args) { return spawnSync(process.execPath, [CLI, ...args], { encoding: "utf8", env: env() }); } test("the CLI: root-file prints the hits and exits 2, or exits 0; gate exits 0, or 2 with the step and its log; anything else is usage", (t) => { const dir = tmp(t); writeFileSync(join(dir, "top"), ["docs", "packages", ""].join("\0")); writeFileSync(join(dir, "bad"), ["docs/a.md", "NEW.md", "src/x", ""].join("\0")); writeFileSync(join(dir, "good"), ["docs/a.md", "packages/p/b.mjs"].join("\0")); let r = cli("root-file", join(dir, "top"), join(dir, "bad")); assert.equal(r.status, 2); assert.equal(r.stdout, "NEW.md\nsrc/x\n"); r = cli("root-file", join(dir, "top"), join(dir, "good")); assert.equal(r.status, 0); assert.equal(r.stdout, ""); const pass = tree(t); r = cli("gate", pass.w, pass.logs); assert.equal(r.status, 0, r.stderr); assert.equal(r.stdout, ""); assert.match(r.stderr, /GATE_PASSED\n$/); const fail = tree(t, { two: "exit 5\n" }); r = cli("gate", fail.w, fail.logs); assert.equal(r.status, 2); assert.equal(r.stdout, `script-two\t${join(fail.logs, "script-two.log")}\n`); for (const args of [[], ["gate", pass.w], ["root-file", "x"], ["land"]]) { r = cli(...args); assert.equal(r.status, 4, args.join(" ")); assert.match(r.stderr, /^usage: landing\.mjs /); } });