New packages/runs reads run records, result.json, the release pointer
and the activation log without writing, pruning or following a link out
of the data root. mosaic-task.mjs list and show use it, so a malformed
result.json or a run that is a regular file no longer crashes them.
Deliberate deltas are listed in the README.
Rocko built it. Round 1 (ed3c5392) was approved with notes by Darkwing
(27115) and Filbert (27116); round 2 (2727198f, tests and wording only)
was approved by Darkwing (27123) and Filbert (27124). Sage's gate on
c9a25a47 plus the candidate: runs 41/0, queue 148/0, webui 22/0,
conversation 182/0 (181/1 in the full run on the K12 cgroup timing
test under load, 182/0 alone), control-board 124/0, every
scripts/test-*.sh 0 failed (task 98/0 with Docker, 26/0 without).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
244 lines
11 KiB
JavaScript
244 lines
11 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import {
|
|
RunsError, isRunId, listRunIds, listRunRecords, readRunDocument, readRunRecord,
|
|
} from "../src/index.mjs";
|
|
import { RESULT, scratch, tree, write } from "./helpers.mjs";
|
|
|
|
const A = "r-20260101T000000Z-aaaaaa";
|
|
const B = "r-20260101T000100Z-bbbbbb";
|
|
|
|
test("isRunId accepts the run id shape and nothing else", () => {
|
|
for (const id of [A, "r-x", "r-zz.weird_name-1", `r-${"a".repeat(64)}`]) assert.equal(isRunId(id), true, id);
|
|
for (const id of ["r-", "r-.x", "r-_x", `r-${"a".repeat(65)}`, "x-1", "r-a/b", "../r-a", "r-a b", 1, null, undefined]) {
|
|
assert.equal(isRunId(id), false, String(id));
|
|
}
|
|
});
|
|
|
|
test("a missing data root or runs directory lists nothing", (t) => {
|
|
const { dir, dataRoot } = scratch(t);
|
|
assert.deepEqual(listRunIds(path.join(dir, "absent")), []);
|
|
assert.deepEqual(listRunIds(dataRoot), []);
|
|
assert.deepEqual(listRunRecords(dataRoot), []);
|
|
});
|
|
|
|
test("runs as a regular file lists nothing, as before", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
write(path.join(dataRoot, "runs"), "not a directory\n");
|
|
assert.deepEqual(listRunIds(dataRoot), []);
|
|
});
|
|
|
|
test("listRunIds keeps r- names only, sorted oldest first", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
for (const name of [B, A, "x-other", "r-zz"]) fs.mkdirSync(path.join(runs, name), { recursive: true });
|
|
write(path.join(runs, ".pruned.log"), "{}\n");
|
|
write(path.join(runs, "r-a-file"), "x\n");
|
|
assert.deepEqual(listRunIds(dataRoot), [A, B, "r-a-file", "r-zz"]);
|
|
});
|
|
|
|
test("listRunRecords returns each result, or null for an incomplete or unreadable one", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
write(path.join(runs, A, "result.json"), RESULT);
|
|
write(path.join(runs, B, "task.json"), { id: "t" });
|
|
write(path.join(runs, "r-c", "result.json"), "{ truncated");
|
|
write(path.join(runs, "r-d", "result.json"), "null\n");
|
|
write(path.join(runs, "r-e", "result.json"), "5\n");
|
|
write(path.join(runs, "r-f", "result.json"), "[1]\n");
|
|
fs.mkdirSync(path.join(runs, "r-g", "result.json"), { recursive: true });
|
|
write(path.join(runs, "r-h"), "a file\n");
|
|
const records = listRunRecords(dataRoot);
|
|
assert.deepEqual(records.map((r) => r.runId), [A, B, "r-c", "r-d", "r-e", "r-f", "r-g", "r-h"]);
|
|
assert.deepEqual(records[0].result, RESULT);
|
|
for (const record of records.slice(1)) assert.equal(record.result, null, record.runId);
|
|
});
|
|
|
|
test("readRunRecord returns documents and artifacts in directory order", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const dir = path.join(dataRoot, "runs", A);
|
|
write(path.join(dir, "result.json"), RESULT);
|
|
write(path.join(dir, "task.json"), { taskVersion: 1, id: "t-one" });
|
|
write(path.join(dir, "mission.json"), { id: "m", objective: "o" });
|
|
write(path.join(dir, "stderr.txt"), "");
|
|
fs.mkdirSync(path.join(dir, "workspace"));
|
|
const record = readRunRecord(dataRoot, A);
|
|
assert.equal(record.runId, A);
|
|
assert.deepEqual(record.result, RESULT);
|
|
assert.deepEqual(record.task, { taskVersion: 1, id: "t-one" });
|
|
assert.deepEqual(record.mission, { id: "m", objective: "o" });
|
|
assert.deepEqual(record.artifacts, fs.readdirSync(dir));
|
|
});
|
|
|
|
test("readRunRecord is null for a missing run, a dangling link or a file", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
assert.equal(readRunRecord(dataRoot, A), null);
|
|
fs.mkdirSync(runs);
|
|
fs.symlinkSync("r-nowhere", path.join(runs, A));
|
|
write(path.join(runs, B), "a file\n");
|
|
assert.equal(readRunRecord(dataRoot, A), null);
|
|
assert.equal(readRunRecord(dataRoot, B), null);
|
|
});
|
|
|
|
test("readRunRecord and readRunDocument refuse an invalid run id before touching the disk", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
for (const id of ["../data", "r-a/../../x", "", "r-"]) {
|
|
assert.throws(() => readRunRecord(dataRoot, id), (e) => e instanceof RunsError && e.exitCode === 4 && /invalid run id/.test(e.message));
|
|
assert.throws(() => readRunDocument(dataRoot, id, "result.json"), RunsError);
|
|
}
|
|
});
|
|
|
|
test("readRunDocument reads only the three run documents", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
write(path.join(dataRoot, "runs", A, "stderr.txt"), "{}\n");
|
|
assert.throws(() => readRunDocument(dataRoot, A, "stderr.txt"), /unknown run document/);
|
|
assert.throws(() => readRunDocument(dataRoot, A, "../../x.json"), /unknown run document/);
|
|
assert.equal(readRunDocument(dataRoot, A, "task.json"), null);
|
|
});
|
|
|
|
test("a link inside the data root is followed", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
write(path.join(runs, A, "result.json"), RESULT);
|
|
fs.symlinkSync(A, path.join(runs, B));
|
|
assert.deepEqual(readRunRecord(dataRoot, B).result, RESULT);
|
|
assert.deepEqual(listRunRecords(dataRoot)[1], { runId: B, result: RESULT });
|
|
});
|
|
|
|
test("a data root that is itself a link is trusted as configured", (t) => {
|
|
const { dir, dataRoot } = scratch(t);
|
|
write(path.join(dataRoot, "runs", A, "result.json"), RESULT);
|
|
const alias = path.join(dir, "alias");
|
|
fs.symlinkSync(dataRoot, alias);
|
|
assert.deepEqual(listRunRecords(alias), [{ runId: A, result: RESULT }]);
|
|
});
|
|
|
|
test("a run directory linked out of the data root is never read", (t) => {
|
|
const { dataRoot, outside } = scratch(t);
|
|
write(path.join(outside, "result.json"), RESULT);
|
|
write(path.join(outside, "task.json"), { id: "t" });
|
|
fs.mkdirSync(path.join(dataRoot, "runs"));
|
|
fs.symlinkSync(outside, path.join(dataRoot, "runs", A));
|
|
assert.deepEqual(listRunRecords(dataRoot), [{ runId: A, result: null }]);
|
|
assert.throws(() => readRunRecord(dataRoot, A), (e) => e instanceof RunsError && e.exitCode === 4 && /resolves outside the data root/.test(e.message));
|
|
});
|
|
|
|
test("a document linked out of the data root reads as null", (t) => {
|
|
const { dataRoot, outside } = scratch(t);
|
|
write(path.join(outside, "secret.json"), RESULT);
|
|
fs.mkdirSync(path.join(dataRoot, "runs", A), { recursive: true });
|
|
for (const name of ["result.json", "task.json", "mission.json"]) {
|
|
fs.symlinkSync(path.join(outside, "secret.json"), path.join(dataRoot, "runs", A, name));
|
|
}
|
|
const record = readRunRecord(dataRoot, A);
|
|
assert.equal(record.result, null);
|
|
assert.equal(record.task, null);
|
|
assert.equal(record.mission, null);
|
|
assert.deepEqual(listRunRecords(dataRoot), [{ runId: A, result: null }]);
|
|
});
|
|
|
|
test("a runs directory linked out of the data root refuses", (t) => {
|
|
const { dataRoot, outside } = scratch(t);
|
|
write(path.join(outside, A, "result.json"), RESULT);
|
|
fs.symlinkSync(outside, path.join(dataRoot, "runs"));
|
|
assert.throws(() => listRunIds(dataRoot), /runs resolves outside the data root/);
|
|
assert.throws(() => listRunRecords(dataRoot), RunsError);
|
|
assert.throws(() => readRunRecord(dataRoot, A), /runs resolves outside the data root/);
|
|
});
|
|
|
|
test("a relative link that climbs out of the data root refuses", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
fs.mkdirSync(path.join(dataRoot, "runs"));
|
|
fs.symlinkSync("../../outside", path.join(dataRoot, "runs", A));
|
|
assert.throws(() => readRunRecord(dataRoot, A), /resolves outside the data root/);
|
|
});
|
|
|
|
test("a sibling whose name starts with the data root's name is outside it", (t) => {
|
|
const { dir, dataRoot } = scratch(t);
|
|
const sibling = path.join(dir, "data-sibling");
|
|
write(path.join(sibling, "result.json"), RESULT);
|
|
fs.mkdirSync(path.join(dataRoot, "runs"));
|
|
fs.symlinkSync(sibling, path.join(dataRoot, "runs", A));
|
|
assert.throws(() => readRunRecord(dataRoot, A), /resolves outside the data root/);
|
|
});
|
|
|
|
test("a link loop refuses instead of reading as missing", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
fs.mkdirSync(path.join(dataRoot, "runs"));
|
|
fs.symlinkSync(B, path.join(dataRoot, "runs", A));
|
|
fs.symlinkSync(A, path.join(dataRoot, "runs", B));
|
|
assert.throws(() => readRunRecord(dataRoot, A), (e) => e instanceof RunsError && /ELOOP/.test(e.message));
|
|
assert.deepEqual(listRunRecords(dataRoot), [{ runId: A, result: null }, { runId: B, result: null }]);
|
|
});
|
|
|
|
test("an unreadable runs directory refuses instead of listing nothing", { skip: process.getuid?.() === 0 && "root reads anything" }, (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
fs.mkdirSync(path.join(runs, A), { recursive: true });
|
|
fs.chmodSync(runs, 0o000);
|
|
try {
|
|
assert.throws(() => listRunIds(dataRoot), (e) => e instanceof RunsError && /EACCES/.test(e.message));
|
|
} finally {
|
|
fs.chmodSync(runs, 0o755);
|
|
}
|
|
});
|
|
|
|
test("an unreadable run directory refuses instead of reading as missing", { skip: process.getuid?.() === 0 && "root reads anything" }, (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const run = path.join(dataRoot, "runs", A);
|
|
write(path.join(run, "result.json"), RESULT);
|
|
fs.chmodSync(run, 0o000);
|
|
try {
|
|
assert.throws(() => readRunRecord(dataRoot, A), (e) => e instanceof RunsError && /EACCES/.test(e.message));
|
|
} finally {
|
|
fs.chmodSync(run, 0o755);
|
|
}
|
|
});
|
|
|
|
test("a link to the data root's parent is outside it", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
fs.mkdirSync(path.join(dataRoot, "runs"));
|
|
fs.symlinkSync("../..", path.join(dataRoot, "runs", A));
|
|
assert.throws(() => readRunRecord(dataRoot, A), /resolves outside the data root/);
|
|
});
|
|
|
|
// readdir order is the filesystem's; node may already return it sorted, so
|
|
// the directory listing is mocked to come back reversed.
|
|
test("listRunIds sorts whatever order the directory returns", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
for (const id of [A, B]) fs.mkdirSync(path.join(dataRoot, "runs", id), { recursive: true });
|
|
const readdirSync = fs.readdirSync;
|
|
t.mock.method(fs, "readdirSync", (...args) => readdirSync(...args).sort().reverse());
|
|
assert.deepEqual(fs.readdirSync(path.join(dataRoot, "runs")), [B, A]);
|
|
assert.deepEqual(listRunIds(dataRoot), [A, B]);
|
|
});
|
|
|
|
// list has always shown any r- name, including ones show refuses as ids.
|
|
test("listRunRecords lists an r- name that isn't a valid run id, as before", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
write(path.join(dataRoot, "runs", "r-.bad", "result.json"), {});
|
|
assert.equal(isRunId("r-.bad"), false);
|
|
assert.deepEqual(listRunIds(dataRoot), ["r-.bad"]);
|
|
assert.deepEqual(listRunRecords(dataRoot), [{ runId: "r-.bad", result: {} }]);
|
|
});
|
|
|
|
test("the readers write nothing", (t) => {
|
|
const { dataRoot } = scratch(t);
|
|
const runs = path.join(dataRoot, "runs");
|
|
write(path.join(runs, A, "result.json"), RESULT);
|
|
write(path.join(runs, A, "task.json"), { id: "t" });
|
|
write(path.join(runs, B, "result.json"), "{ bad");
|
|
fs.symlinkSync(A, path.join(runs, "r-link"));
|
|
const before = tree(dataRoot);
|
|
listRunIds(dataRoot);
|
|
listRunRecords(dataRoot);
|
|
readRunRecord(dataRoot, A);
|
|
readRunRecord(dataRoot, B);
|
|
readRunRecord(dataRoot, "r-absent");
|
|
readRunDocument(dataRoot, A, "mission.json");
|
|
assert.equal(tree(dataRoot), before);
|
|
});
|