101 lines
4.5 KiB
JavaScript
101 lines
4.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import * as fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, test } from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const project = fileURLToPath(new URL("../", import.meta.url));
|
|
const fixtures = [];
|
|
|
|
afterEach(async () => {
|
|
for (const root of fixtures.splice(0)) {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
async function fixture() {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "galaxy-install-preflight-"));
|
|
fixtures.push(root);
|
|
const app = path.join(root, "script/build-and-install-to-applications");
|
|
const lib = path.join(root, "script/lib");
|
|
const bin = path.join(root, "bin");
|
|
await fs.mkdir(path.join(app, "src"), { recursive: true });
|
|
await fs.mkdir(lib, { recursive: true });
|
|
await fs.mkdir(bin);
|
|
await fs.copyFile(path.join(project, "src/index.tsx"), path.join(app, "src/index.tsx"));
|
|
await fs.copyFile(path.join(project, "package.json"), path.join(app, "package.json"));
|
|
await fs.copyFile(path.join(project, "../lib/prepare-galaxy-build.mjs"), path.join(lib, "prepare-galaxy-build.mjs"));
|
|
await fs.symlink(path.join(project, "node_modules"), path.join(app, "node_modules"), "dir");
|
|
// A regression must never progress into a real Git, build, or install operation.
|
|
const gitSentinel = path.join(root, "unexpected-git-command");
|
|
await fs.writeFile(path.join(bin, "git"), "#!/bin/sh\ntouch unexpected-git-command\nexit 97\n", { mode: 0o755 });
|
|
const run = () => {
|
|
const result = spawnSync(process.execPath, [path.join(project, "node_modules/tsx/dist/cli.mjs"), path.join(app, "src/index.tsx")], {
|
|
cwd: root,
|
|
env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH || ""}`, CI: "true", FORCE_COLOR: "0" },
|
|
encoding: "utf8",
|
|
timeout: 15000,
|
|
});
|
|
assert.equal(result.error, undefined);
|
|
assert.equal(result.status, 1);
|
|
assert.match(result.stdout, /Installation failed/);
|
|
return result;
|
|
};
|
|
return { root, lib, run, gitSentinel };
|
|
}
|
|
|
|
test("cache initialization errors are visible before any normal step starts", async () => {
|
|
const { root, run, gitSentinel } = await fixture();
|
|
await fs.mkdir(path.join(root, "target"));
|
|
await fs.writeFile(path.join(root, "target/galaxy-build-cache"), "not a directory");
|
|
const result = run();
|
|
assert.match(result.stderr, /Installation failed:.*EEXIST/);
|
|
assert.match(result.stderr, /galaxy-build-cache/);
|
|
await assert.rejects(fs.access(gitSentinel));
|
|
});
|
|
|
|
test("stale locks report the exact lock path and retain the lock for manual recovery", async () => {
|
|
const { root, run, gitSentinel } = await fixture();
|
|
const lock = path.join(root, "target/galaxy-build-cache/prepare.lock");
|
|
await fs.mkdir(path.dirname(lock), { recursive: true });
|
|
await fs.symlink(JSON.stringify({ pid: 99999999, start: "old process", token: "abandoned" }), lock);
|
|
const result = run();
|
|
assert.match(result.stderr, /Installation failed: Stale Galaxy build lock/);
|
|
assert.ok(result.stderr.includes(lock));
|
|
assert.ok((await fs.lstat(lock)).isSymbolicLink());
|
|
await assert.rejects(fs.access(gitSentinel));
|
|
});
|
|
|
|
test("failures without an active step still appear in the final output", async () => {
|
|
const { lib, run, gitSentinel } = await fixture();
|
|
await fs.writeFile(path.join(lib, "prepare-galaxy-build.mjs"), `
|
|
export async function prepareGalaxyBuild() {
|
|
throw new Error("fixture early failure");
|
|
}
|
|
`);
|
|
const result = run();
|
|
assert.match(result.stderr, /Installation failed: fixture early failure/);
|
|
assert.match(result.stdout, /Installation failed: fixture early failure/);
|
|
await assert.rejects(fs.access(gitSentinel));
|
|
});
|
|
|
|
test("cleanup failures are reported without hiding the original failure", async () => {
|
|
const { root, lib, run, gitSentinel } = await fixture();
|
|
await fs.writeFile(path.join(lib, "prepare-galaxy-build.mjs"), `
|
|
export async function prepareGalaxyBuild(root) {
|
|
return {
|
|
appPath: root + "/missing/Galaxy.app",
|
|
release: async () => { throw new Error("fixture cleanup failure"); },
|
|
};
|
|
}
|
|
`);
|
|
const result = run();
|
|
assert.match(result.stderr, /Installation failed: Built app not found/);
|
|
assert.match(result.stderr, /Failed to remove temporary build files: fixture cleanup failure/);
|
|
assert.match(result.stdout, /Installation failed:\s*Built app not found/);
|
|
await assert.rejects(fs.access(gitSentinel));
|
|
await assert.rejects(fs.access(path.join(root, "target")));
|
|
});
|