Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2955d6607 |
@@ -108,6 +108,12 @@ class ReplayDeniedError extends Error {
|
||||
}
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
// `Intl` and `Date.prototype.toLocaleString` read the host timezone, so the
|
||||
// same artifact replayed on two machines produced different output — fatal
|
||||
// for a deterministic replay. Pin the timezone before anything touches a
|
||||
// date. Verified: with the host set to Asia/Tokyo the observed values become
|
||||
// UTC and epoch respectively, independent of the machine.
|
||||
process.env.TZ = "UTC";
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
||||
const rawRequest: unknown = JSON.parse(
|
||||
@@ -396,6 +402,13 @@ const projectValueRecursive = (
|
||||
};
|
||||
|
||||
const projectLeaf = (candidate: unknown): unknown => {
|
||||
if (candidate === undefined) {
|
||||
// JSON has no undefined: `JSON.stringify([undefined])` is `[null]` and the
|
||||
// array-hole projection already yields null. Mapping it here keeps one
|
||||
// value for "no value" instead of rejecting `[undefined]` while accepting
|
||||
// `[ , ]` for the same position.
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
candidate === null ||
|
||||
typeof candidate === "string" ||
|
||||
|
||||
@@ -497,6 +497,57 @@ describe("deterministic replay Date compatibility", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps undefined to null so a position is never silently unrepresentable", async () => {
|
||||
const workerRequest = await request("parser.mjs", "esm", "default", []);
|
||||
workerRequest.left.modules[0] = {
|
||||
alias: "entry",
|
||||
format: "esm",
|
||||
dependencies: {},
|
||||
source: "export default function () { return [undefined, 1]; }",
|
||||
};
|
||||
const result = await runWorker(workerRequest);
|
||||
expect(JSON.parse(result.stdout)).toMatchObject({
|
||||
left: [{ outcome: "return", value: [null, 1] }],
|
||||
});
|
||||
});
|
||||
|
||||
it("still rejects values JSON cannot represent at all", async () => {
|
||||
const workerRequest = await request("parser.mjs", "esm", "default", []);
|
||||
workerRequest.left.modules[0] = {
|
||||
alias: "entry",
|
||||
format: "esm",
|
||||
dependencies: {},
|
||||
source: "export default function () { return [10n]; }",
|
||||
};
|
||||
const result = await runWorker(workerRequest);
|
||||
expect(JSON.parse(result.stdout)).toMatchObject({
|
||||
left: [
|
||||
{
|
||||
outcome: "serialization_error",
|
||||
exception: { message: "Unsupported replay result type: bigint" },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("pins the timezone so locale output is machine independent", async () => {
|
||||
const workerRequest = await request("parser.mjs", "esm", "default", []);
|
||||
workerRequest.left.modules[0] = {
|
||||
alias: "entry",
|
||||
format: "esm",
|
||||
dependencies: {},
|
||||
source:
|
||||
"export default function () { return [new Intl.DateTimeFormat().resolvedOptions().timeZone, new Date(0).toLocaleString()]; }",
|
||||
};
|
||||
const result = await runWorker(workerRequest);
|
||||
const outcome = JSON.parse(result.stdout).left[0];
|
||||
expect(outcome.outcome).toBe("return");
|
||||
expect(outcome.value[0]).toBe("UTC");
|
||||
// The rendered clock is 12:00 UTC regardless of the host zone. The exact
|
||||
// wording is ICU-build dependent, so only the pinned hour is asserted.
|
||||
expect(outcome.value[1]).toContain("12:00:00");
|
||||
});
|
||||
|
||||
describe("replay array projection", () => {
|
||||
it.each([
|
||||
["Array(3)", [null, null, null]],
|
||||
|
||||
Reference in New Issue
Block a user