From 0c3a3204ca8a4d507a79323fc33edcdfec4e21dd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 7 Oct 2026 18:32:59 -0500 Subject: [PATCH] Complete worker ABI and validation coverage - Align shared-buffer layouts and native worker declarations. - Complete runtime link and worker lifecycle contracts. - Check fixture baselines before running parity suites. --- AGENTS.md | 2 + packages/compiler/src/backend/llvm/emitter.ts | 4 +- .../test/bytes-element-emission.test.ts | 2 +- .../compiler/test/llvm-runtime-abi.test.ts | 10 +- .../test/ts7/baselines/order-parity.json | 120 ++++++++++++++++++ .../compiler/test/ts7/order-parity.test.ts | 19 +-- packages/runtime/src/scr_json.test.ts | 1 + packages/runtime/src/scr_runtime.h | 8 ++ packages/runtime/src/scr_worker.h | 5 - packages/runtime/test/array.test.ts | 6 + packages/runtime/test/bytes.test.ts | 1 + packages/runtime/test/closure.test.ts | 6 + packages/runtime/test/inspect.test.ts | 6 + packages/runtime/test/json.test.ts | 1 + packages/runtime/test/lib.test.ts | 1 + packages/runtime/test/map.test.ts | 6 + packages/runtime/test/path.test.ts | 5 + packages/runtime/test/regex.test.ts | 1 + packages/runtime/test/runtime.test.ts | 1 + packages/runtime/test/string.test.ts | 6 + packages/runtime/test/tonumber.test.ts | 6 + packages/runtime/test/url.test.ts | 3 + scripts/test-ts7.mjs | 19 +++ scripts/ts7-order-fixtures.mjs | 21 +++ tests/corpus/native-worker-exit-listeners.ts | 13 +- .../harness/self-hosting-llvm-emitter.test.ts | 34 +---- tests/harness/ts7-baselines.test.ts | 55 ++++++++ 27 files changed, 303 insertions(+), 59 deletions(-) create mode 100644 scripts/ts7-order-fixtures.mjs create mode 100644 tests/harness/ts7-baselines.test.ts diff --git a/AGENTS.md b/AGENTS.md index 26305f340..9be4eb548 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,8 @@ SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # sanitized lane Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways. +Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep. + Test location follows scope: - Co-locate white-box unit tests with implementation files under diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6bf743424..476808e93 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1568,11 +1568,11 @@ export class LlEmitter { // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24, // payload at 32). `%ScrCaught = type { ${this.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`, - // ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed + // ScrBytes { rc, len, elem(i32+pad), data, backing, brands, shared }. Indexed // typed-array access GEPs through this directly: the IR type already // fixes elem, so the hot path needs neither a runtime kind load nor // the generic scr_bytes_get/set call. - `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8 }`, + `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`, // The capture box { rc, kind, obj_retain, obj_release, obj_trace, // slot } — TDZ reads peek the payload slot (offset 40) directly. `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`, diff --git a/packages/compiler/test/bytes-element-emission.test.ts b/packages/compiler/test/bytes-element-emission.test.ts index cce060f8f..9c40b0aeb 100644 --- a/packages/compiler/test/bytes-element-emission.test.ts +++ b/packages/compiler/test/bytes-element-emission.test.ts @@ -271,7 +271,7 @@ test("LLVM emission performs typed-array element access directly on the valid pa const mod = fixture(); expect(validateModule(mod)).toEqual([]); const ll = emitLlvmModule(mod); - expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8 }"); + expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }"); expect(ll).toContain("getelementptr inbounds i8"); expect(ll).toContain("getelementptr inbounds i32"); expect(ll).toContain("getelementptr inbounds float"); diff --git a/packages/compiler/test/llvm-runtime-abi.test.ts b/packages/compiler/test/llvm-runtime-abi.test.ts index 63a609386..7bec5f72d 100644 --- a/packages/compiler/test/llvm-runtime-abi.test.ts +++ b/packages/compiler/test/llvm-runtime-abi.test.ts @@ -61,6 +61,8 @@ const EMIT_FIXTURES = [ "996-fs-rc-stress.ts", "997-fs-modules/main.ts", "1006-json-fs-config.ts", + "native-worker-roots/main.ts", + "native-worker-shared-views.ts", ]; interface CProto { @@ -158,6 +160,7 @@ function splitParams(argsText: string): string[] { async function parseHeader(): Promise<{ protos: Map; dataSyms: Set }> { const raw = await readFile(headerPath, "utf8"); const src = raw + .replace(/\\\r?\n/g, "") .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/\/\/[^\n]*/g, " ") .replace(/^[ \t]*#[^\n]*$/gm, " "); @@ -317,19 +320,22 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => { #include #include "scr_runtime.h" -/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8 }. +/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }. * Keep every ABI fact used by its field-index GEPs explicit here. */ _Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t"); _Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers"); _Static_assert(sizeof(ScrBytesElem) == 4, "LLVM ScrBytes elem field is i32"); _Static_assert(_Alignof(ScrBytes) == 8, "LLVM ScrBytes alignment changed"); -_Static_assert(sizeof(ScrBytes) == 48, "LLVM ScrBytes size changed"); +_Static_assert(sizeof(ScrBytes) == 56, "LLVM ScrBytes size changed"); _Static_assert(offsetof(ScrBytes, rc) == 0, "LLVM ScrBytes.rc offset changed"); _Static_assert(offsetof(ScrBytes, len) == 8, "LLVM ScrBytes.len offset changed"); _Static_assert(offsetof(ScrBytes, elem) == 16, "LLVM ScrBytes.elem offset changed"); _Static_assert(offsetof(ScrBytes, data) == 24, "LLVM ScrBytes.data offset changed"); _Static_assert(offsetof(ScrBytes, backing) == 32, "LLVM ScrBytes.backing offset changed"); _Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer offset changed"); +_Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed"); +_Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed"); +_Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed"); `, ); const driver = resolveCc(); diff --git a/packages/compiler/test/ts7/baselines/order-parity.json b/packages/compiler/test/ts7/baselines/order-parity.json index b1e45c223..1c25a7273 100644 --- a/packages/compiler/test/ts7/baselines/order-parity.json +++ b/packages/compiler/test/ts7/baselines/order-parity.json @@ -14517,6 +14517,126 @@ "/tests/corpus/scalar-string-slices.ts" ], "diags": [] + }, + "/tests/corpus/atomics-integer-operations.ts": { + "order": [ + "/tests/corpus/atomics-integer-operations.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-buffer-consumers.ts": { + "order": [ + "/tests/corpus/native-worker-buffer-consumers.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-clone-errors.ts": { + "order": [ + "/tests/corpus/native-worker-clone-errors.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-commonjs.cjs": { + "order": [ + "/tests/corpus/native-worker-commonjs.cjs" + ], + "diags": [] + }, + "/tests/corpus/native-worker-delayed-messages.ts": { + "order": [ + "/tests/corpus/native-worker-delayed-messages.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-environment.ts": { + "order": [ + "/tests/corpus/native-worker-environment.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-error.ts": { + "order": [ + "/tests/corpus/native-worker-error.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-exit-listeners.ts": { + "order": [ + "/tests/corpus/native-worker-exit-listeners.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-io.ts": { + "order": [ + "/tests/corpus/native-worker-io.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-loop-cancellation.ts": { + "order": [ + "/tests/corpus/native-worker-loop-cancellation.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-message-graphs.ts": { + "order": [ + "/tests/corpus/native-worker-message-graphs.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-messages.ts": { + "order": [ + "/tests/corpus/native-worker-messages.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-port-lifetime.ts": { + "order": [ + "/tests/corpus/native-worker-port-lifetime.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-process-exit.ts": { + "order": [ + "/tests/corpus/native-worker-process-exit.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-references.ts": { + "order": [ + "/tests/corpus/native-worker-references.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-roots/main.ts": { + "order": [ + "/tests/corpus/native-worker-roots/main.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-shared-views.ts": { + "order": [ + "/tests/corpus/native-worker-shared-views.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-terminate-loop.ts": { + "order": [ + "/tests/corpus/native-worker-terminate-loop.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-terminate-suspended.ts": { + "order": [ + "/tests/corpus/native-worker-terminate-suspended.ts" + ], + "diags": [] + }, + "/tests/corpus/native-worker-terminate-wait.ts": { + "order": [ + "/tests/corpus/native-worker-terminate-wait.ts" + ], + "diags": [] } } } diff --git a/packages/compiler/test/ts7/order-parity.test.ts b/packages/compiler/test/ts7/order-parity.test.ts index 1ae83323f..3f888c5f0 100644 --- a/packages/compiler/test/ts7/order-parity.test.ts +++ b/packages/compiler/test/ts7/order-parity.test.ts @@ -38,7 +38,7 @@ * sweep and the upgrade playbook's step 2. */ import { execFile } from "node:child_process"; -import { globSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; @@ -47,6 +47,7 @@ import { afterAll, describe, expect, test } from "vitest"; import { checkPreflightTs7 } from "../../src/frontend/program-node.js"; import { Ts7Host } from "../../src/frontend/ts7/program-adapter.js"; import type { ScrDiagnostic } from "../../src/diagnostics/diagnostic.js"; +import { allOrderEntries, orderEntriesUnder } from "../../../../scripts/ts7-order-fixtures.mjs"; const repoRoot = join(import.meta.dirname, "../../../.."); const baselinePath = join(import.meta.dirname, "baselines/order-parity.json"); @@ -111,25 +112,13 @@ async function nativeAnswers(entries: string[]): Promise { } function entriesUnder(dir: string): string[] { - const exts = ["ts", "js", "mjs", "cjs"]; - return exts - .flatMap((ext) => [ - ...globSync(join(repoRoot, dir, `*.${ext}`)), - ...globSync(join(repoRoot, dir, `*/main.${ext}`)), - ]) - .sort(); + return orderEntriesUnder(repoRoot, dir); } const FULL = process.env["SCRIPTC_TS7_ALL"] === "1" || UPDATE; function allEntries(): string[] { - return [ - ...entriesUnder("tests/corpus"), - ...entriesUnder("tests/diagnostics"), - ...entriesUnder("tests/fixtures/npm/cases"), - ...globSync(join(repoRoot, "tests/fixtures/strictness/*/main.ts")).sort(), - ...globSync(join(repoRoot, "tests/fixtures/node-types/*.ts")).sort(), - ]; + return allOrderEntries(repoRoot); } function pickEntries(): string[] { diff --git a/packages/runtime/src/scr_json.test.ts b/packages/runtime/src/scr_json.test.ts index af140f0d9..5ba23b397 100644 --- a/packages/runtime/src/scr_json.test.ts +++ b/packages/runtime/src/scr_json.test.ts @@ -41,6 +41,7 @@ test.each([false, true])( "scr_cycle.c", "scr_lib.c", "scr_bytes.c", + "scr_shared.c", "scr_bigint.c", "scr_url.c", "scr_url_params.c", diff --git a/packages/runtime/src/scr_runtime.h b/packages/runtime/src/scr_runtime.h index 0edd143e1..c78e34560 100644 --- a/packages/runtime/src/scr_runtime.h +++ b/packages/runtime/src/scr_runtime.h @@ -63,6 +63,7 @@ void scr_init(void); * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ void scr_runtime_abi_v7(void); +void scr_runtime_workers_v7(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── * Every unrecoverable runtime trap — OOM, semantic range traps, internal- @@ -304,6 +305,10 @@ struct ScrArr *scr_context_env_pairs(void); /* Also used by ordinary event-loop builds, where this always returns true. */ bool scr_context_is_main(void); +double scr_context_thread_number(void); +double scr_worker_root(void); +int scr_worker_argc(void); +char **scr_worker_argv(void); #ifdef SCR_WORKERS #define scr_getenv(name) scr_context_getenv(name) @@ -5914,6 +5919,9 @@ void scr_bytes_require_unshared(const ScrBytes *); #define SCR_BYTES_SNAPSHOT(name) ((void)0) #endif ScrDyn *scr_shared_array_buffer_new(ScrDyn *); +ScrDyn *scr_worker_data(void); +ScrDyn *scr_worker_parent_port(void); +ScrDyn *scr_worker_new(double root, ScrStr *filename, ScrDyn *options); bool scr_shared_array_buffer_is(const ScrDyn *); bool scr_buffer_storage_is(const ScrDyn *); ScrBytes *scr_bytes_from_external(void *data, size_t length); diff --git a/packages/runtime/src/scr_worker.h b/packages/runtime/src/scr_worker.h index 53a972cb9..cc00a8f02 100644 --- a/packages/runtime/src/scr_worker.h +++ b/packages/runtime/src/scr_worker.h @@ -36,11 +36,6 @@ bool scr_worker_post_parent(ScrMessage *message); void scr_worker_port_close(void); /* Current-thread access: inbox is borrowed, data is a retained local value. */ ScrMailbox *scr_worker_inbox(void); -ScrDyn *scr_worker_data(void); -ScrDyn *scr_worker_parent_port(void); -ScrDyn *scr_worker_new(double root, ScrStr *filename, ScrDyn *options); -int scr_worker_argc(void); -char **scr_worker_argv(void); void scr_loop_set_workers(bool (*pending)(void), bool (*ready)(void), void (*dispatch)(void), int (*pollfd)(void), void (*wait)(double)); diff --git a/packages/runtime/test/array.test.ts b/packages/runtime/test/array.test.ts index 97bed0a9b..af024ee3b 100644 --- a/packages/runtime/test/array.test.ts +++ b/packages/runtime/test/array.test.ts @@ -33,6 +33,12 @@ beforeAll(async () => { join(testDir, "../src/scr_string.c"), join(testDir, "../src/scr_number.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_path.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/packages/runtime/test/bytes.test.ts b/packages/runtime/test/bytes.test.ts index 33dc5b3bb..42588f740 100644 --- a/packages/runtime/test/bytes.test.ts +++ b/packages/runtime/test/bytes.test.ts @@ -32,6 +32,7 @@ beforeAll(async () => { join(testDir, "test_bytes.c"), join(testDir, "../src/scr_bytes.test.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), join(testDir, "../src/scr_bytes_io.c"), join(testDir, "../src/scr_zlib.c"), join(testDir, "../src/scr_lib.c"), diff --git a/packages/runtime/test/closure.test.ts b/packages/runtime/test/closure.test.ts index e58f5e260..0f238ae3e 100644 --- a/packages/runtime/test/closure.test.ts +++ b/packages/runtime/test/closure.test.ts @@ -32,6 +32,12 @@ test("closure/box runtime: RC cascades clean under ASan + audit", async () => { join(srcDir, "scr_number.c"), join(srcDir, "scr_cycle.c"), join(srcDir, "scr_bytes.c"), + join(srcDir, "scr_shared.c"), + join(srcDir, "scr_lib.c"), + join(srcDir, "scr_console.c"), + join(srcDir, "scr_path.c"), + join(srcDir, "scr_url.c"), + join(srcDir, "scr_url_params.c"), join(srcDir, "scr_error.c"), join(srcDir, "scr_exception.c"), join(srcDir, "scr_object.c"), diff --git a/packages/runtime/test/inspect.test.ts b/packages/runtime/test/inspect.test.ts index 94403a5eb..658d3174c 100644 --- a/packages/runtime/test/inspect.test.ts +++ b/packages/runtime/test/inspect.test.ts @@ -36,6 +36,12 @@ beforeAll(async () => { join(testDir, "../src/scr_number.c"), join(testDir, "../src/scr_array.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_path.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/packages/runtime/test/json.test.ts b/packages/runtime/test/json.test.ts index 60c021845..ba9ffe20f 100644 --- a/packages/runtime/test/json.test.ts +++ b/packages/runtime/test/json.test.ts @@ -43,6 +43,7 @@ beforeAll(async () => { join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_path.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), ...(process.platform === "linux" ? ["-D_GNU_SOURCE", "-lm"] : []), ]); }); diff --git a/packages/runtime/test/lib.test.ts b/packages/runtime/test/lib.test.ts index 88c9c86ec..729ea3605 100644 --- a/packages/runtime/test/lib.test.ts +++ b/packages/runtime/test/lib.test.ts @@ -46,6 +46,7 @@ beforeAll(async () => { join(testDir, "../src/scr_json.c"), join(testDir, "../src/scr_bigint.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), ...(process.platform === "linux" ? ["-lm"] : []), ]); scratch = await mkdtemp(join(tmpdir(), "scriptc-lib-")); diff --git a/packages/runtime/test/map.test.ts b/packages/runtime/test/map.test.ts index 72d0c84ac..cc8830c15 100644 --- a/packages/runtime/test/map.test.ts +++ b/packages/runtime/test/map.test.ts @@ -38,6 +38,12 @@ beforeAll(async () => { join(testDir, "../src/scr_closure.c"), join(testDir, "../src/scr_object.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_path.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), ...(process.platform === "linux" ? ["-D_GNU_SOURCE", "-lm"] : []), diff --git a/packages/runtime/test/path.test.ts b/packages/runtime/test/path.test.ts index 51569fc46..ada14c451 100644 --- a/packages/runtime/test/path.test.ts +++ b/packages/runtime/test/path.test.ts @@ -36,6 +36,11 @@ beforeAll(async () => { join(testDir, "../src/scr_bigint.c"), join(testDir, "../src/scr_closure.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/packages/runtime/test/regex.test.ts b/packages/runtime/test/regex.test.ts index 6f3a5f328..1bd037180 100644 --- a/packages/runtime/test/regex.test.ts +++ b/packages/runtime/test/regex.test.ts @@ -51,6 +51,7 @@ beforeAll(async () => { join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_path.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), ...(process.platform === "linux" ? ["-D_GNU_SOURCE", "-lm"] : []), ]); }, 60_000); diff --git a/packages/runtime/test/runtime.test.ts b/packages/runtime/test/runtime.test.ts index 4a3f3e5cb..b7ec196be 100644 --- a/packages/runtime/test/runtime.test.ts +++ b/packages/runtime/test/runtime.test.ts @@ -14,6 +14,7 @@ const RUNTIME_SOURCES = [ "scr_string.c", "scr_array.c", "scr_bytes.c", + "scr_shared.c", "scr_bytes_io.c", "scr_map.c", "scr_closure.c", diff --git a/packages/runtime/test/string.test.ts b/packages/runtime/test/string.test.ts index 30ddf80c8..28f74699f 100644 --- a/packages/runtime/test/string.test.ts +++ b/packages/runtime/test/string.test.ts @@ -37,6 +37,12 @@ beforeAll(async () => { join(testDir, "../src/scr_bigint.c"), join(testDir, "../src/scr_closure.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_path.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/packages/runtime/test/tonumber.test.ts b/packages/runtime/test/tonumber.test.ts index db3664ad9..6278dbec0 100644 --- a/packages/runtime/test/tonumber.test.ts +++ b/packages/runtime/test/tonumber.test.ts @@ -44,6 +44,12 @@ test("scr_string_to_number matches Node Number(s) on committed oracle cases", as join(testDir, "../src/scr_bigint.c"), join(testDir, "../src/scr_closure.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), + join(testDir, "../src/scr_path.c"), + join(testDir, "../src/scr_url.c"), + join(testDir, "../src/scr_url_params.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/packages/runtime/test/url.test.ts b/packages/runtime/test/url.test.ts index 09c2f60f9..672d69e25 100644 --- a/packages/runtime/test/url.test.ts +++ b/packages/runtime/test/url.test.ts @@ -36,6 +36,9 @@ beforeAll(async () => { join(testDir, "../src/scr_bigint.c"), join(testDir, "../src/scr_closure.c"), join(testDir, "../src/scr_bytes.c"), + join(testDir, "../src/scr_shared.c"), + join(testDir, "../src/scr_lib.c"), + join(testDir, "../src/scr_console.c"), join(testDir, "../src/scr_error.c"), join(testDir, "../src/scr_exception.c"), join(testDir, "../src/scr_object.c"), diff --git a/scripts/test-ts7.mjs b/scripts/test-ts7.mjs index 599fa7420..bad8e557b 100644 --- a/scripts/test-ts7.mjs +++ b/scripts/test-ts7.mjs @@ -4,9 +4,28 @@ import { readdirSync, readFileSync } from "node:fs"; import { dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; +import { allOrderEntries } from "./ts7-order-fixtures.mjs"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const baseline = JSON.parse( + readFileSync(resolve(repoRoot, "packages/compiler/test/ts7/baselines/order-parity.json"), "utf8"), +); +const missingBaselines = allOrderEntries(repoRoot) + .map((path) => relative(repoRoot, path).replaceAll("\\", "/")) + .filter((path) => !Object.hasOwn(baseline.entries, `/${path}`)); +if (missingBaselines.length > 0) { + console.error(`Missing TypeScript preflight/order baselines:\n${missingBaselines.join("\n")}`); + console.error( + "Record the new fixtures with SCRIPTC_UPDATE_BASELINES=1 pnpm exec vitest run packages/compiler/test/ts7/order-parity.test.ts, review the diff, then run the affected parity batches before opening a PR.", + ); + process.exit(1); +} +if (process.argv.includes("--baselines-only")) { + console.log("Every TypeScript preflight/order fixture has a recorded baseline."); + process.exit(0); +} + const ALLOWED_TYPESCRIPT5_IMPORTS = new Set([ "packages/compiler/src/frontend/comptime-node.ts", "packages/compiler/src/frontend/ts7/world-check.ts", diff --git a/scripts/ts7-order-fixtures.mjs b/scripts/ts7-order-fixtures.mjs new file mode 100644 index 000000000..a22a80692 --- /dev/null +++ b/scripts/ts7-order-fixtures.mjs @@ -0,0 +1,21 @@ +import { globSync } from "node:fs"; +import { join } from "node:path"; + +export function orderEntriesUnder(repoRoot, dir) { + return ["ts", "js", "mjs", "cjs"] + .flatMap((ext) => [ + ...globSync(join(repoRoot, dir, `*.${ext}`)), + ...globSync(join(repoRoot, dir, `*/main.${ext}`)), + ]) + .sort(); +} + +export function allOrderEntries(repoRoot) { + return [ + ...orderEntriesUnder(repoRoot, "tests/corpus"), + ...orderEntriesUnder(repoRoot, "tests/diagnostics"), + ...orderEntriesUnder(repoRoot, "tests/fixtures/npm/cases"), + ...globSync(join(repoRoot, "tests/fixtures/strictness/*/main.ts")).sort(), + ...globSync(join(repoRoot, "tests/fixtures/node-types/*.ts")).sort(), + ]; +} diff --git a/tests/corpus/native-worker-exit-listeners.ts b/tests/corpus/native-worker-exit-listeners.ts index 10af21a59..c100d432f 100644 --- a/tests/corpus/native-worker-exit-listeners.ts +++ b/tests/corpus/native-worker-exit-listeners.ts @@ -4,9 +4,16 @@ if (isMainThread) { for (const mode of ["nested", "throw", "natural"]) { await new Promise((resolve) => { const worker = new Worker(new URL(import.meta.url), {workerData: mode}); - worker.on("message", (value: unknown) => console.log(mode, "message", value)); - worker.on("error", (error: Error) => console.log(mode, "error", error.message)); - worker.on("exit", (code: number) => { console.log(mode, "exit", code); resolve(); }); + let message = -1; + let messages = 0; + let failure = "none"; + // Message and error use independent channels; both must arrive before exit. + worker.on("message", (value: number) => { message = value; messages++; }); + worker.on("error", (error: Error) => { failure = error.message; }); + worker.on("exit", (code: number) => { + console.log(mode, "exit", code, "messages", messages, message, "error", failure); + resolve(); + }); }); } } else { diff --git a/tests/harness/self-hosting-llvm-emitter.test.ts b/tests/harness/self-hosting-llvm-emitter.test.ts index 2312b9d20..98fbeca26 100644 --- a/tests/harness/self-hosting-llvm-emitter.test.ts +++ b/tests/harness/self-hosting-llvm-emitter.test.ts @@ -14,19 +14,7 @@ import { } from "@scriptc/compiler"; import { emitLlvmModule } from "../../packages/compiler/src/backend/llvm/emitter.js"; import { type IrModule } from "../../packages/compiler/src/ir/ir.js"; -import { - moduleUsesAssert, - moduleUsesBigInt, - moduleUsesCopying, - moduleUsesDynInvoke, - moduleUsesEmitter, - moduleUsesInspect, - moduleUsesLegacyTextDecoder, - moduleUsesRegex, - moduleUsesStream, - moduleUsesSymbol, - moduleUsesZlib, -} from "../../packages/compiler/src/ir/runtime-features.js"; +import { executableLinkFeatures } from "../../packages/compiler/src/backend/executable-features.js"; import { llvmEmitterCases, llvmEmitterOptions, @@ -51,22 +39,6 @@ function programStderr(stderr: Buffer): string { : text; } -function nativeFeatures(mod: IrModule) { - return { - regex: moduleUsesRegex(mod), - copying: moduleUsesCopying(mod), - inspect: moduleUsesInspect(mod), - dynInvoke: moduleUsesDynInvoke(mod), - symbol: moduleUsesSymbol(mod), - bigint: moduleUsesBigInt(mod), - zlib: moduleUsesZlib(mod), - assert: moduleUsesAssert(mod), - emitter: moduleUsesEmitter(mod), - stream: moduleUsesStream(mod), - textDecoderLegacy: moduleUsesLegacyTextDecoder(mod), - }; -} - // Execute generated modules across expression dispatch, shared mutable // emitter state, exception paths, dynamic walkers and callback adapters. const programs = [ @@ -253,7 +225,7 @@ for (const backend of ["llvm"] as const) { outPath, sanitize, optimization: "dev", - ...nativeFeatures(mod), + ...executableLinkFeatures(mod, false), }); const node = spawnSync(process.execPath, [sourcePath], runOptions); const native = spawnSync(outPath, [], runOptions); @@ -278,7 +250,7 @@ for (const backend of ["llvm"] as const) { outPath: secondStage, sanitize, optimization: "dev", - ...nativeFeatures(ownIr), + ...executableLinkFeatures(ownIr, false), }); const again = join(dir, "again.ll"); const second = await execFileAsync(secondStage, [input, again, config], { diff --git a/tests/harness/ts7-baselines.test.ts b/tests/harness/ts7-baselines.test.ts new file mode 100644 index 000000000..5382d5a0c --- /dev/null +++ b/tests/harness/ts7-baselines.test.ts @@ -0,0 +1,55 @@ +import { spawnSync } from "node:child_process"; +import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { expect, test } from "vitest"; + +test("baseline preflight reports every missing fixture before loading the compiler", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-baseline-preflight-")); + const put = (path: string, content: string) => { + const fullPath = join(root, path); + mkdirSync(dirname(fullPath), { recursive: true }); + writeFileSync(fullPath, content); + }; + try { + mkdirSync(join(root, "scripts")); + for (const name of ["test-ts7.mjs", "ts7-order-fixtures.mjs"]) { + copyFileSync(join(import.meta.dirname, "../../scripts", name), join(root, "scripts", name)); + } + const fixtures = [ + "tests/corpus/plain.ts", + "tests/corpus/common.cjs", + "tests/corpus/modules/main.mjs", + "tests/diagnostics/refused.ts", + "tests/fixtures/npm/cases/example/main.js", + "tests/fixtures/strictness/strict/main.ts", + "tests/fixtures/node-types/api.ts", + ]; + for (const path of [...fixtures, "tests/corpus/modules/helper.ts"]) put(path, ""); + const baselinePath = "packages/compiler/test/ts7/baselines/order-parity.json"; + put(baselinePath, JSON.stringify({ entries: {} })); + const run = () => + spawnSync(process.execPath, [join(root, "scripts/test-ts7.mjs"), "--baselines-only"], { + encoding: "utf8", + }); + const missing = run(); + expect(missing.status).toBe(1); + for (const path of fixtures) expect(missing.stderr).toContain(path); + expect(missing.stderr).not.toContain("helper.ts"); + expect(missing.stderr).toContain("SCRIPTC_UPDATE_BASELINES=1"); + + put( + baselinePath, + JSON.stringify({ + entries: Object.fromEntries( + fixtures.map((path) => [`/${path}`, { order: [], diags: [] }]), + ), + }), + ); + const complete = run(); + expect(complete.status, complete.stderr).toBe(0); + expect(complete.stdout).toContain("Every TypeScript preflight/order fixture"); + } finally { + rmSync(root, { recursive: true, force: true }); + } +});