171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { createHash } from "node:crypto";
|
|
import {
|
|
copyFile,
|
|
cp,
|
|
mkdir,
|
|
mkdtemp,
|
|
readFile,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
buildPerlPack,
|
|
installPerlPack,
|
|
PERL_ENGINE_LOCK,
|
|
verifyPerlPack,
|
|
} from "./perl-engine-pack.mjs";
|
|
|
|
const temporaryDirectories: string[] = [];
|
|
const installedPack = path.resolve("public", "engines", "perl");
|
|
const packFiles = [
|
|
"LICENSE-Devel-StackTrace-Artistic-2.0.txt",
|
|
"LICENSE-Emscripten.txt",
|
|
"LICENSE-WebPerl-Artistic.txt",
|
|
"LICENSE-WebPerl-GPL.txt",
|
|
"NOTICE.txt",
|
|
"SHA256SUMS",
|
|
"emperl.data",
|
|
"emperl.js",
|
|
"emperl.wasm",
|
|
"engine-metadata.json",
|
|
"perl-runtime.worker.js",
|
|
"regex_tools_bridge.pl",
|
|
] as const;
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories
|
|
.splice(0)
|
|
.map((directory) => rm(directory, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
async function digest(file: string): Promise<string> {
|
|
return createHash("sha256")
|
|
.update(await readFile(file))
|
|
.digest("hex");
|
|
}
|
|
|
|
async function makeExtractedPrebuilt(parent: string): Promise<string> {
|
|
const prebuilt = path.join(parent, "webperl_prebuilt_v0.09-beta");
|
|
await mkdir(prebuilt, { recursive: true });
|
|
for (const name of ["emperl.data", "emperl.js", "emperl.wasm"]) {
|
|
await copyFile(path.join(installedPack, name), path.join(prebuilt, name));
|
|
}
|
|
await copyFile(
|
|
path.join(installedPack, "LICENSE-WebPerl-Artistic.txt"),
|
|
path.join(prebuilt, "LICENSE_artistic.txt"),
|
|
);
|
|
await copyFile(
|
|
path.join(installedPack, "LICENSE-WebPerl-GPL.txt"),
|
|
path.join(prebuilt, "LICENSE_gpl.txt"),
|
|
);
|
|
return prebuilt;
|
|
}
|
|
|
|
describe("Perl engine-pack gate", () => {
|
|
it("verifies the installed closed WebPerl pack in a strict-CSP Chromium worker", async () => {
|
|
await expect(verifyPerlPack(installedPack)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
engineVersion: "5.28.1",
|
|
status: "legacy-beta-compatibility-runtime",
|
|
source: expect.objectContaining({
|
|
tag: "v0.09-beta",
|
|
commitSha1: "6f2173d29a2c2e3536e1de75ff5d291ae96ab348",
|
|
}),
|
|
bridge: expect.objectContaining({
|
|
replacementOutputTransport: expect.stringContaining(
|
|
"fixed MEMFS binary file",
|
|
),
|
|
limits: expect.objectContaining({
|
|
maximumResponseJsonBytes: 8_388_608,
|
|
outputChunkCharacters: 4_096,
|
|
}),
|
|
}),
|
|
securityAudit: expect.objectContaining({
|
|
generatedLoaderJavaScriptEvalSites: 1,
|
|
generatedLoaderNewFunctionSites: 0,
|
|
}),
|
|
}),
|
|
);
|
|
}, 60_000);
|
|
|
|
it("pins the exact archive, runtime assets, and legacy identity", () => {
|
|
expect(PERL_ENGINE_LOCK.engineVersion).toBe("5.28.1");
|
|
expect(PERL_ENGINE_LOCK.webPerlVersion).toBe("0.09-beta");
|
|
expect(PERL_ENGINE_LOCK.prebuilt.archiveSha256).toBe(
|
|
"5f441249217e90ab378c666f473d4206ab4f44907f6bb0aa8d70834bc38c40dc",
|
|
);
|
|
expect(PERL_ENGINE_LOCK.prebuilt.files["emperl.wasm"]).toEqual(
|
|
expect.objectContaining({
|
|
bytes: 3_734_063,
|
|
sha256:
|
|
"f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("builds byte-identical offline packs and installs one atomically", async () => {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-pack-"));
|
|
temporaryDirectories.push(directory);
|
|
const prebuilt = await makeExtractedPrebuilt(directory);
|
|
const first = path.join(directory, "first");
|
|
const second = path.join(directory, "second");
|
|
|
|
await buildPerlPack({ prebuiltDirectory: prebuilt, output: first });
|
|
await buildPerlPack({ prebuiltDirectory: prebuilt, output: second });
|
|
|
|
for (const file of packFiles) {
|
|
expect(await digest(path.join(first, file)), file).toBe(
|
|
await digest(path.join(second, file)),
|
|
);
|
|
}
|
|
|
|
const fakeRoot = path.join(directory, "repository");
|
|
await mkdir(path.join(fakeRoot, "engines"), { recursive: true });
|
|
await cp(
|
|
path.resolve("engines", "perl"),
|
|
path.join(fakeRoot, "engines", "perl"),
|
|
{ recursive: true },
|
|
);
|
|
const oldTarget = path.join(fakeRoot, "public", "engines", "perl");
|
|
await mkdir(oldTarget, { recursive: true });
|
|
await writeFile(path.join(oldTarget, "old-marker"), "old\n", "utf8");
|
|
|
|
const installed = await installPerlPack(first, fakeRoot);
|
|
|
|
expect(installed.output).toBe(oldTarget);
|
|
await expect(
|
|
readFile(path.join(oldTarget, "old-marker")),
|
|
).rejects.toThrow();
|
|
await expect(
|
|
verifyPerlPack(oldTarget, fakeRoot, { smoke: false }),
|
|
).resolves.toMatchObject({ engineVersion: "5.28.1" });
|
|
}, 120_000);
|
|
|
|
it("rejects extra pack entries and drifted extracted inputs", async () => {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-reject-"));
|
|
temporaryDirectories.push(directory);
|
|
const pack = path.join(directory, "pack");
|
|
await cp(installedPack, pack, { recursive: true });
|
|
await writeFile(path.join(pack, "unexpected"), "not allowed\n", "utf8");
|
|
await expect(
|
|
verifyPerlPack(pack, path.resolve("."), { smoke: false }),
|
|
).rejects.toThrow("must contain only");
|
|
|
|
const prebuilt = await makeExtractedPrebuilt(directory);
|
|
await writeFile(path.join(prebuilt, "emperl.wasm"), "not wasm\n");
|
|
await expect(
|
|
buildPerlPack({
|
|
prebuiltDirectory: prebuilt,
|
|
output: path.join(directory, "rejected"),
|
|
}),
|
|
).rejects.toThrow("pinned WebPerl release");
|
|
});
|
|
});
|