Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2955d6607 |
@@ -108,6 +108,12 @@ class ReplayDeniedError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const main = async (): Promise<void> => {
|
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[] = [];
|
const chunks: Buffer[] = [];
|
||||||
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
||||||
const rawRequest: unknown = JSON.parse(
|
const rawRequest: unknown = JSON.parse(
|
||||||
@@ -396,6 +402,13 @@ const projectValueRecursive = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const projectLeaf = (candidate: unknown): unknown => {
|
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 (
|
if (
|
||||||
candidate === null ||
|
candidate === null ||
|
||||||
typeof candidate === "string" ||
|
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", () => {
|
describe("replay array projection", () => {
|
||||||
it.each([
|
it.each([
|
||||||
["Array(3)", [null, null, null]],
|
["Array(3)", [null, null, null]],
|
||||||
|
|||||||
Reference in New Issue
Block a user