Files
scriptc/tests/harness/intl-missing-data.test.ts
T
Chris Tate 7329f07d36 Match V8's default locale and calendar in the ICU formatters, and build Intl programs without Zig from the runtime pack
- scr_intl.c resolves the default locale as V8 does: ICU's POSIX locale
  (LC_ALL=C, C.UTF-8, POSIX) becomes en-US, so numbers keep their grouping
  separators, and other defaults round-trip through their BCP 47 tag. The
  number formatter, the date pattern generator, the date formatter and the
  default word segmenter use that locale.
- The date formatter moves the Gregorian calendar's Julian cutover to the
  start of ECMAScript time, so dates before October 1582 print their
  proleptic Gregorian fields, as in Node.
- icu.numFormatDefault, icu.dateLocaleString and icu.dateLocaleTimeString
  are in the may-throw set: a missing ICU data file throws an Error into the
  surrounding catch.
- `x.toLocaleString(undefined)` reads the receiver before the argument runs.
- The linux-x64-gnu runtime pack ships the ICU headers scr_intl.c includes
  beside its ICU archive (manifest `headers`, verified like the other
  artifacts). The native compiler's sanitized build compiles scr_intl.c
  against them and refuses with SC3003 when they are missing or changed.
  The C-source path (sanitized builds) links the pack's ICU archive and
  uses those headers, instead of building ICU with Zig's C++ driver.
- The macOS pack omits scr_intl.c, scr_intl.h declares ScrDyn, and the
  library-mode teachings test uses a locale argument, which still refuses
  with SC2020.

Corpus programs pin the C and C.UTF-8 locales, pre-1582 dates and the
receiver order against Node. intl-missing-data.test.ts covers the throw.
sanitizer.test.ts covers the header lookup.
2026-10-29 02:42:35 -05:00

74 lines
2.6 KiB
TypeScript

/* The default-locale ICU formatters (scr_intl.c) throw an Error when the
* ICU data file is missing. Node cannot reach this state (its data is inside
* the binary), so the test runs the compiled program without the data file
* beside it and with ICU_DATA pointing at an empty directory: each throw
* enters the surrounding catch, directly and through a closure, and the
* program continues after it. */
import { execFile } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";
import { compile } from "@scriptc/compiler";
const execFileAsync = promisify(execFile);
const PROGRAM = `
function attempt(label: string, run: () => string): void {
try {
console.log(label, run());
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.log(label, "caught", error instanceof Error, message.includes("icudt78l.dat"));
}
}
attempt("number", () => (1234.5).toLocaleString());
attempt("date", () => new Date(0).toLocaleString());
attempt("time", () => new Date(0).toLocaleTimeString());
try {
const text = (42).toLocaleString();
console.log("direct", text);
} catch {
console.log("direct caught");
}
console.log("after");
`;
test.skipIf(process.platform !== "linux" || process.arch !== "x64")(
"a missing ICU data file throws into the surrounding catch",
async () => {
const dir = mkdtempSync(join(tmpdir(), "scriptc-intl-missing-data-"));
try {
const entry = join(dir, "main.ts");
writeFileSync(entry, PROGRAM);
const result = await compile(entry, {
outPath: join(dir, "out", "main"),
outDir: join(dir, "out"),
sanitize: process.env["SCRIPTC_SAN"] === "1",
});
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
if (!result.ok) return;
rmSync(join(dirname(result.binaryPath), "icudt78l.dat"), { force: true });
const empty = join(dir, "no-data");
mkdirSync(empty);
const { stdout } = await execFileAsync(result.binaryPath, [], {
env: { ...process.env, ICU_DATA: empty },
});
expect(stdout).toBe(
[
"number caught true true",
"date caught true true",
"time caught true true",
"direct caught",
"after",
"",
].join("\n"),
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
300_000,
);