mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 11:56:12 +00:00
* test(ci): require provider coverage and reuse test sessions * fix(ci): prepare the runtime before Python producer tests The fast Python job ran the suite on a fresh checkout, so analysis-efficiency checks failed without compiled process modules. Install locked dependencies and prepare the runtime before the suite. * fix(ci): keep provider test reports inside the checkout The provider-report reader accepted an arbitrary path from argv. Resolve the path and reject anything outside the checkout before reading it. The packet and signal workflows write those reports under .cache. * fix(ci): require a test module in each named Python bridge family Discovery still runs every bridge test. The mitmproxy, native, and pwndbg families must each keep a test module, so deleting one cannot pass through the others. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
216 lines
6.6 KiB
TypeScript
216 lines
6.6 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import {
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { expect, it } from "vitest";
|
|
import { parse } from "yaml";
|
|
import { z } from "zod";
|
|
|
|
const script = fileURLToPath(
|
|
new URL("../../scripts/ci/require-provider-tests.mjs", import.meta.url),
|
|
);
|
|
const cleanupCases = [
|
|
"capture",
|
|
"tool",
|
|
"limiter",
|
|
"kernel",
|
|
"field-tool",
|
|
"field-limiter",
|
|
"field-kernel",
|
|
].map(
|
|
(item) =>
|
|
`retains failed ${item} descriptors and retries them before further acquisition and shutdown`,
|
|
);
|
|
const commonLimits = [
|
|
"enforces both address-space limits before any producer allocation",
|
|
"preserves lower inherited file/CPU limits in the real launch",
|
|
];
|
|
const testFile = (path: string, names: string[]) => ({
|
|
name: resolve(path),
|
|
assertionResults: names.map((fullName) => ({ fullName, status: "passed" })),
|
|
});
|
|
const report = (lane: "packet" | "signal") => ({
|
|
success: true,
|
|
testResults:
|
|
lane === "packet"
|
|
? [
|
|
testFile("tests/boundary/captures/resourceLimits.test.ts", [
|
|
...commonLimits,
|
|
"reports a real kernel file-limit termination with its effective ceiling",
|
|
]),
|
|
testFile(
|
|
"tests/boundary/captures/captureDescriptorCleanup.test.ts",
|
|
cleanupCases,
|
|
),
|
|
]
|
|
: [
|
|
testFile(
|
|
"tests/boundary/signals/resourceLimits.test.ts",
|
|
commonLimits,
|
|
),
|
|
testFile(
|
|
"tests/boundary/signals/recordingDescriptorCleanup.test.ts",
|
|
cleanupCases,
|
|
),
|
|
],
|
|
});
|
|
const run = (lane: string, value: unknown) => {
|
|
mkdirSync(join(process.cwd(), ".cache"), { recursive: true });
|
|
const directory = mkdtempSync(
|
|
join(process.cwd(), ".cache", "rea-required-cases-"),
|
|
);
|
|
try {
|
|
const path = join(directory, "report.json");
|
|
writeFileSync(path, JSON.stringify(value));
|
|
return execFileSync(process.execPath, [script, lane, path], {
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
};
|
|
const outsideReportFailure = (reportPath: string) => {
|
|
try {
|
|
execFileSync(process.execPath, [script, "packet", reportPath], {
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
return undefined;
|
|
} catch (error) {
|
|
return error;
|
|
}
|
|
};
|
|
|
|
it.each(["packet", "signal"] as const)(
|
|
"requires the %s resource and descriptor cases without banning unrelated skips",
|
|
(lane) => {
|
|
const result = report(lane);
|
|
result.testResults.push({
|
|
name: resolve("tests/unrelated.test.ts"),
|
|
assertionResults: [{ fullName: "host-specific", status: "pending" }],
|
|
});
|
|
expect(run(lane, result)).toContain("cases passed");
|
|
for (const resultIndex of [0, 1]) {
|
|
const skipped = report(lane);
|
|
skipped.testResults[resultIndex]!.assertionResults[0]!.status = "pending";
|
|
expect(() => run(lane, skipped)).toThrow(
|
|
"Required provider test did not pass",
|
|
);
|
|
const failed = report(lane);
|
|
failed.testResults[resultIndex]!.assertionResults[0]!.status = "failed";
|
|
expect(() => run(lane, failed)).toThrow(
|
|
"Required provider test did not pass",
|
|
);
|
|
const removed = report(lane);
|
|
removed.testResults[resultIndex]!.assertionResults.shift();
|
|
expect(() => run(lane, removed)).toThrow(
|
|
"Required provider test did not pass",
|
|
);
|
|
const duplicate = report(lane);
|
|
duplicate.testResults[resultIndex]!.assertionResults.push(
|
|
duplicate.testResults[resultIndex]!.assertionResults[0]!,
|
|
);
|
|
expect(() => run(lane, duplicate)).toThrow(
|
|
"Required provider test did not pass",
|
|
);
|
|
}
|
|
const missingFile = report(lane);
|
|
missingFile.testResults.pop();
|
|
expect(() => run(lane, missingFile)).toThrow(
|
|
"Expected exactly one provider test result",
|
|
);
|
|
expect(() => run(lane, { success: true, testResults: [] })).toThrow();
|
|
expect(() => run(lane, { success: false, testResults: [] })).toThrow();
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["parent", resolve(process.cwd(), "..", "rea-provider-report.json")],
|
|
["sibling prefix", `${resolve(process.cwd())}-outside/report.json`],
|
|
["absolute outside", resolve(tmpdir(), "rea-provider-report.json")],
|
|
] as const)(
|
|
"rejects a %s report path outside the checkout",
|
|
(_label, reportPath) => {
|
|
const failure = z
|
|
.object({ status: z.literal(1), stderr: z.string() })
|
|
.parse(outsideReportFailure(reportPath));
|
|
const encoded = failure.stderr.match(
|
|
/\{"code":"report_path_outside_checkout"\}/u,
|
|
)?.[0];
|
|
expect(JSON.parse(encoded ?? "")).toEqual({
|
|
code: "report_path_outside_checkout",
|
|
});
|
|
},
|
|
);
|
|
|
|
it("rejects unknown lanes and missing or malformed reports", () => {
|
|
expect(() => run("other", report("packet"))).toThrow(
|
|
"Expected provider test lane",
|
|
);
|
|
expect(() => run("packet", null)).toThrow(
|
|
"missing a successful test inventory",
|
|
);
|
|
expect(() =>
|
|
execFileSync(process.execPath, [script, "packet", "/missing/report.json"], {
|
|
stdio: "pipe",
|
|
}),
|
|
).toThrow();
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
"real-packet-capture.yml",
|
|
"tshark",
|
|
"packet",
|
|
"tests/boundary/captures/resourceLimits.test.ts",
|
|
"tests/boundary/captures/captureDescriptorCleanup.test.ts",
|
|
],
|
|
[
|
|
"real-signal-recording.yml",
|
|
"sigrok",
|
|
"signal",
|
|
"tests/boundary/signals/resourceLimits.test.ts",
|
|
"tests/boundary/signals/recordingDescriptorCleanup.test.ts",
|
|
],
|
|
] as const)(
|
|
"keeps required case validation in %s",
|
|
(workflow, job, lane, limits, cleanup) => {
|
|
const file = new URL(
|
|
`../../.github/workflows/${workflow}`,
|
|
import.meta.url,
|
|
);
|
|
const parsed = z
|
|
.object({
|
|
jobs: z.record(
|
|
z.string(),
|
|
z.object({
|
|
steps: z.array(z.object({ run: z.string().optional() })),
|
|
}),
|
|
),
|
|
})
|
|
.parse(parse(readFileSync(file, "utf8")));
|
|
const step = parsed.jobs[job]?.steps.find((item) =>
|
|
item.run?.includes("scripts/ci/require-provider-tests.mjs"),
|
|
);
|
|
expect(step?.run).toContain('rm -f "$report"');
|
|
expect(step?.run).toContain("node scripts/run-exact-tests.mjs run");
|
|
expect(step?.run).toContain("--reporter=json");
|
|
expect(step?.run).toContain('--outputFile.json="$report"');
|
|
expect(step?.run).toContain(limits);
|
|
expect(step?.run).toContain(cleanup);
|
|
expect(step?.run).toContain(
|
|
`node scripts/ci/require-provider-tests.mjs ${lane} "$report"`,
|
|
);
|
|
},
|
|
);
|