Files
rea/tests/process-global/asarSnapshotOperation.test.ts
morluto 026126eafe fix(artifacts): serialize ASAR snapshot ownership
Serialize preparation and close for each reader so concurrent callers share one owned snapshot. Preserve per-call cancellation and expected digest checks, cleanup retry ownership, and reuse after close.

Validation: 34 ASAR boundary tests, one fork-isolated snapshot race regression, test discovery, and check:fast passed.
2026-10-11 09:28:28 +08:00

65 lines
2.2 KiB
TypeScript

import { createHash } from "node:crypto";
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { buffer } from "node:stream/consumers";
import { createPackage } from "@electron/asar";
import { afterEach, expect, it, vi } from "vitest";
import { AsarArtifactReader } from "../../src/artifacts/AsarArtifactReader.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
afterEach(() => {
vi.unstubAllEnvs();
});
it("serializes concurrent ASAR snapshot preparation and cleans its single owned root", async () => {
const root = await createTestTempDirectory("rea-asar-snapshot-owner-");
vi.stubEnv("TMPDIR", root);
vi.stubEnv("TMP", root);
vi.stubEnv("TEMP", root);
const source = join(root, "source");
const archive = join(root, "app.asar");
await mkdir(source);
await writeFile(join(source, "main.js"), "module.exports = 42;\n");
await createPackage(source, archive);
const reader = new AsarArtifactReader(archive);
const iterator = reader.entries()[Symbol.asyncIterator]();
try {
const expectedDigest = createHash("sha256")
.update(await readFile(archive))
.digest("hex");
const openingContainer = reader.openContainer();
const readingEntry = iterator.next();
const preparingSnapshot = reader.prepareContainer(expectedDigest);
const [snapshot, firstEntry] = await Promise.all([
openingContainer,
readingEntry,
preparingSnapshot,
]);
if (firstEntry.done) throw new Error("Expected an ASAR member");
expect(await buffer(snapshot)).toEqual(await readFile(archive));
const mismatch = reader.prepareContainer("0".repeat(64));
const matching = reader.prepareContainer(expectedDigest);
await expect(mismatch).rejects.toMatchObject({ reason: "integrity" });
await expect(matching).resolves.toBeUndefined();
const snapshotRoots = async (): Promise<string[]> =>
(await readdir(root)).filter((name) =>
name.startsWith("rea-asar-snapshot-"),
);
expect(await snapshotRoots()).toHaveLength(1);
await reader.close();
expect(await snapshotRoots()).toEqual([]);
} finally {
try {
await iterator.return?.();
} finally {
await reader.close();
}
}
});