| 1 | const assert = require("node:assert/strict"); |
| 2 | const fs = require("node:fs"); |
| 3 | const os = require("node:os"); |
| 4 | const path = require("node:path"); |
| 5 | const { spawnSync } = require("node:child_process"); |
| 6 | const { test } = require("node:test"); |
| 7 | |
| 8 | const sourceRoot = path.resolve(__dirname, "../.."); |
| 9 | |
| 10 | function fixture(t) { |
| 11 | const root = fs.mkdtempSync(path.join(os.tmpdir(), "cw-d8-launch-")); |
| 12 | t.after(() => fs.rmSync(root, { recursive: true, force: true })); |
| 13 | const repo = path.join(root, "source"); |
| 14 | fs.mkdirSync(path.join(repo, "scripts"), { recursive: true }); |
| 15 | for (const name of ["dev-test.sh", "dev-cache.sh", "dev-cargo.sh", "with-hermetic-test-home.sh", "build-lock.py"]) { |
| 16 | fs.copyFileSync(path.join(sourceRoot, "scripts", name), path.join(repo, "scripts", name)); |
| 17 | fs.chmodSync(path.join(repo, "scripts", name), 0o755); |
| 18 | } |
| 19 | fs.writeFileSync(path.join(repo, "Cargo.toml"), '[workspace]\nmembers = []\n'); |
| 20 | const bin = path.join(root, "bin"); |
| 21 | const home = path.join(root, "ambient home"); |
| 22 | fs.mkdirSync(bin); fs.mkdirSync(home); |
| 23 | const log = path.join(root, "cargo-invocations.jsonl"); |
| 24 | fs.writeFileSync(path.join(bin, "cargo"), `#!${process.execPath} |
| 25 | |
| 26 | const fs = require("node:fs"); |
| 27 | const args = process.argv.slice(2); |
| 28 | if (args.includes("--version")) { console.log("cargo 1.97.0 (source fixture)"); process.exit(0); } |
| 29 | fs.appendFileSync(process.env.D8_INVOCATIONS, JSON.stringify({args, home: process.env.HOME, cache: process.env.CODEWHALE_CACHE_ROOT}) + "\\n"); |
| 30 | if (args.includes("build") && process.env.D8_BUILD_FAILURE) process.exit(Number(process.env.D8_BUILD_FAILURE)); |
| 31 | console.log("test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s"); |
| 32 | `); |
| 33 | fs.writeFileSync(path.join(bin, "rustc"), `#!/bin/sh\nprintf '%s\n' 'rustc 1.97.0 (source fixture)' 'commit-hash: source-fixture'\n`); |
| 34 | fs.writeFileSync(path.join(bin, "rustup"), `#!/bin/sh\nif [ "$1" = which ] && [ "$2" = rustc ]; then printf '%s\n' "$D8_TOOLCHAIN/rustc"; else exit 99; fi\n`); |
| 35 | for (const name of ["cargo", "rustc", "rustup"]) fs.chmodSync(path.join(bin, name), 0o755); |
| 36 | return { |
| 37 | home, log, |
| 38 | run(area, failure) { |
| 39 | const result = spawnSync("sh", [path.join(repo, "scripts/dev-test.sh"), area, "filter with spaces"], { |
| 40 | cwd: repo, encoding: "utf8", |
| 41 | env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`, HOME: home, |
| 42 | USERPROFILE: home, CARGO_HOME: path.join(root, "cargo-home"), RUSTUP_HOME: path.join(root, "rustup-home"), |
| 43 | CODEWHALE_DEV_NEXTEST: "0", CODEWHALE_BUILD_LOCK: "0", CODEWHALE_BUILD_LOCK_HELD: "", |
| 44 | CODEWHALE_CACHE_ROOT: path.join(root, "cache"), D8_INVOCATIONS: log, D8_TOOLCHAIN: bin, |
| 45 | D8_BUILD_FAILURE: failure ? String(failure) : "" }, |
| 46 | }); |
| 47 | const calls = fs.existsSync(log) ? fs.readFileSync(log, "utf8").trim().split("\n").map(JSON.parse) : []; |
| 48 | return { result, calls }; |
| 49 | }, |
| 50 | }; |
| 51 | } |
| 52 | |
| 53 | for (const area of ["tui-integration", "tui-cucumber"]) { |
| 54 | test(`${area} builds the canonical executable before isolated acceptance`, (t) => { |
| 55 | const f = fixture(t); const { result, calls } = f.run(area); |
| 56 | assert.equal(result.status, 0, result.stderr + result.stdout); |
| 57 | assert.equal(calls.length, 2); |
| 58 | const build = calls[0].args.slice(calls[0].args.indexOf("build")); |
| 59 | assert.deepEqual(build, ["build", "-p", "codewhale-cli", "--bin", "codewhale", "--locked"]); |
| 60 | const check = calls[1].args.slice(calls[1].args.indexOf("test")); |
| 61 | assert.deepEqual(check, ["test", "-p", "codewhale-tui", "--test", area === "tui-integration" ? "integration" : "cucumber", "--locked", "filter with spaces"]); |
| 62 | for (const call of calls) { |
| 63 | assert.notEqual(call.home, f.home); |
| 64 | assert.equal(fs.existsSync(call.home), false, "temporary test HOME is removed"); |
| 65 | assert.ok(call.cache.endsWith("cache")); |
| 66 | } |
| 67 | }); |
| 68 | } |
| 69 | |
| 70 | test("canonical build failure prevents acceptance and preserves its exit status", (t) => { |
| 71 | const f = fixture(t); const { result, calls } = f.run("tui-integration", 39); |
| 72 | assert.equal(result.status, 39, result.stderr + result.stdout); |
| 73 | assert.equal(calls.length, 1); |
| 74 | assert.ok(calls[0].args.includes("build")); |
| 75 | }); |
| 76 | |
| 77 | test("library-only verification does not build an executable", (t) => { |
| 78 | const f = fixture(t); const { result, calls } = f.run("config"); |
| 79 | assert.equal(result.status, 0, result.stderr + result.stdout); |
| 80 | assert.equal(calls.length, 1); |
| 81 | assert.ok(calls[0].args.includes("test")); |
| 82 | assert.ok(calls[0].args.includes("--lib")); |
| 83 | }); |
| 84 |