Skip to content

test(parity): add node:process granular suite - #1331

Merged
proggeramlug merged 1 commit into
mainfrom
feat/process-node-suite
May 23, 2026
Merged

test(parity): add node:process granular suite#1331
proggeramlug merged 1 commit into
mainfrom
feat/process-node-suite

Conversation

@proggeramlug

@proggeramlug proggeramlug commented May 22, 2026

Copy link
Copy Markdown
Contributor

Comprehensive node:process node-suite (there was none before). 41 granular cases covering the deterministic surface, validated against node --experimental-strip-types. Tests use the global process (the primary API). This branch also carries #1371's runtime fixes (merged in), so it lands tests and impl together.

Passing (24/41)

object shape · platform/arch · pid · ppid · env reads + Object.keys · cwd() · version + versions.node/v8 · argv shape · nextTick ordering/returns-undefined · hrtime() tuple + bigint · memoryUsage() · uptime() · stdout/stderr write + fds · global identity · method-value typeof · env writes · argv0/execPath/title (bold = fixed by #1371).

Gaps (17/41) — each tracked by its own granular issue

Every failure is recorded in known_failures.json pointing at its specific issue (no umbrellas). No version bump / CHANGELOG.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Final-pass update: suite is now 52 cases, 28 pass. Added kill/chdir/debugPort/argv[1] (pass) and report/connected/memoryUsage.rss/getBuiltinModule/loadEnvFile/sourceMapsEnabled/title-set (gaps → #1395#1401). Every failure maps to a granular issue in known_failures.json; cross-checked no stale/untracked.

proggeramlug added a commit that referenced this pull request May 22, 2026
…sweep (#1414)

Rolls up 26 PRs that merged to main post-v0.5.1023 without version
bumps:

- node:crypto gap-fixes (#1386 #1393 #1394 #1402 #1405): randomInt,
  timingSafeEqual, getHashes/getCiphers, sha224/sha384, base64 digest,
  Buffer hash input, no-arg digest() → Buffer, pbkdf2Sync digest arg,
  scryptSync.
- node:perf_hooks (#1321 + #1328 #1342 coverage): performance + User
  Timing + PerformanceObserver native impl, granular node-suite +
  edge-case coverage.
- #1090 GC checkpoint runtime work (#1324).
- #1311 geisterhand on iOS (#1316 #1383 #1384 #1385).
- #1312 process.env.X (unset) is nullish undefined (#1314).
- #1319 thread-safety hardening for cross-thread runtime statics.
- #1322 exact-head GC evidence packet.
- #1323 wasm timers dispatch through mem_call bridge (#1329).
- #1317 node:timers/promises shadow-segfault fix (#1326).
- #1330 node:process suite (#1331).
- #1292 bcrypt.hash() returns String (#1307).
- #1293 fastify .json()/.body external-fastify dispatch (#1308).
- #1296 app pattern performance gaps.
- #1297 diagnostics_channel parity.
- #1301 iOS App Groups capability (#1313).
- #1318 #1325 os/methods/modern-methods static dispatch.
- #1315 expanded Node parity test coverage.
- #1382 ui-ios stdlib pump for async fetch.
- #1392 ui-wasm reactive state + setText (#1404).
proggeramlug added a commit that referenced this pull request May 23, 2026
…test global-timer pollution

CI flake on #1331 (`event_pump::tests::wait_returns_when_timer_due`
returning in 426µs instead of ~50ms). `js_wait_for_event`'s wait budget
is derived from GLOBAL timer state and `SERIAL` only serializes the
event_pump tests against each other — a concurrently-running non-serialized
test (a timer/promise test in another module) can inject a sooner deadline
or notify and shorten any single measurement. Pollution never lengthens a
wait past its 50ms budget, so a working wait blocks ~50ms on at least one
un-polluted attempt.

Replace the single-shot measurement with the file's established
retry-until-clean shape (same as `sustained_budget_zero_spin_is_throttled`):
drain leaked timer/notify state at the start of each attempt, schedule
our 50ms timer, measure, and accept the test as soon as one attempt
lands in [40ms, 500ms). Stays deterministic against a real budget bug
(which would return early on *every* attempt) while tolerating the
cross-test race.

Verified: full multi-threaded `cargo test -p perry-runtime` passes
(wait_returns_when_timer_due OK); 5× targeted stress all green.
@proggeramlug
proggeramlug force-pushed the feat/process-node-suite branch from a084b37 to a83ac81 Compare May 23, 2026 09:36
proggeramlug added a commit that referenced this pull request May 23, 2026
…e_parity.sh

First exhaustive node:process suite on main: 60 granular cases (39 pass /
22 fail) covering shape, platform, env, cwd, version + versions sub-fields,
argv, nextTick, hrtime + bigint, props, memory + rss + available, cpu +
threadCpuUsage, uptime, stdout/stderr fds + write, global identity, release,
exec-argv, exit + exit-code, events (on/emit/listeners), methods
(umask/abort/emit-warning/kill/chdir/get-builtin-module/load-env-file/
source-maps-enabled/dlopen/ref-unref/exit), resource (usage + active),
features, config, ipc (connected/send-disconnect), report, allowed flags,
title, sub-fields, unix uid/gid, module-load-list, finalization, exception
uncaught-capture, shapes ppid/debug-port/method-types/process-object/
module-load-list, exit-code, hrtime tuple form.

22 failing cases tracked in known_failures.json with granular per-gap issue
references. Method-value reflection (#1343), env-write (#1344), hrtime()
tuple (#1345), and argv0/execPath/title (#1346) were originally fixed in
#1371 but that PR merged into the #1331 branch only — #1331 was rebased
onto current main and those fixes were dropped; the issues are reopened.

Adds run_module_parity.sh: thin wrapper that runs ./run_parity_tests.sh
--suite node-suite --module <m> for a list of modules (default: process +
perf_hooks) and prints a combined per-module + total summary.
…e_parity.sh

First exhaustive node:process suite on main: 60 granular cases (39 pass /
22 fail) covering shape, platform, env, cwd, version + versions sub-fields,
argv, nextTick, hrtime + bigint, props, memory + rss + available, cpu +
threadCpuUsage, uptime, stdout/stderr fds + write, global identity, release,
exec-argv, exit + exit-code, events (on/emit/listeners), methods
(umask/abort/emit-warning/kill/chdir/get-builtin-module/load-env-file/
source-maps-enabled/dlopen/ref-unref/exit), resource (usage + active),
features, config, ipc (connected/send-disconnect), report, allowed flags,
title, sub-fields, unix uid/gid, module-load-list, finalization, exception
uncaught-capture, shapes ppid/debug-port/method-types/process-object/
module-load-list, exit-code, hrtime tuple form.

22 failing cases tracked in known_failures.json with granular per-gap issue
references. Method-value reflection (#1343), env-write (#1344), hrtime()
tuple (#1345), and argv0/execPath/title (#1346) were originally fixed in
onto current main and those fixes were dropped; the issues are reopened.

Adds run_module_parity.sh: thin wrapper that runs ./run_parity_tests.sh
--suite node-suite --module <m> for a list of modules (default: process +
perf_hooks) and prints a combined per-module + total summary.
@proggeramlug
proggeramlug force-pushed the feat/process-node-suite branch from a83ac81 to 63014c4 Compare May 23, 2026 09:39
@proggeramlug
proggeramlug merged commit 8510555 into main May 23, 2026
17 checks passed
@proggeramlug
proggeramlug deleted the feat/process-node-suite branch May 23, 2026 09:45
proggeramlug added a commit that referenced this pull request May 23, 2026
Node's `process.dlopen(module, filename, flags?)` is the native-addon
(.node) loader. Perry was reading it as a bare number, so
`typeof process.dlopen === "function"` returned false and call sites
crashed with "value is not a function".

Perry statically links every dependency at compile time — there's no
dynamic loader to wire up. The closest no-op is to return undefined:
optional-dep wrappers that feature-detect on `typeof process.dlopen
=== "function"` first see a "loaded" non-error and fall back to their
pure-JS path; real addon-loading attempts surface as undefined exports
downstream.

- Call lowering (`native_module.rs`): folds to `Expr::Undefined`,
  sitting next to the `ref` / `unref` / `setSourceMapsEnabled` /
  `getBuiltinModule` arms.
- Typeof fold (`lower_expr.rs`): adds `dlopen` to the same
  process-method-as-function list.

Test: test-parity/node-suite/process/methods/dlopen.ts (already in
the granular suite from #1331) now passes — byte-identical to
`node --experimental-strip-types`.
proggeramlug added a commit that referenced this pull request May 23, 2026
Node exposes `process.hasUncaughtExceptionCaptureCallback()` (boolean
getter) + `process.setUncaughtExceptionCaptureCallback(fn|null)`
(installs/clears a single capture callback that intercepts uncaught
exceptions before the `uncaughtException` event fires). Perry was
reading both as bare numbers — `typeof` lied with `"number"` and any
call crashed with "value is not a function".

Perry doesn't expose the underlying capture hook, so both behave as
no-ops:

- `hasUncaughtExceptionCaptureCallback()` → `Expr::Bool(false)`.
- `setUncaughtExceptionCaptureCallback(fn|null)` → `Expr::Undefined`.

Both names are added to the typeof-fold list in `lower_expr.rs` so
feature-detection (`typeof process.X === "function"`) returns
`"function"`, sitting next to the `ref` / `unref` /
`setSourceMapsEnabled` / `getBuiltinModule` / `dlopen` arms.

Test: test-parity/node-suite/process/exception/uncaught-capture.ts
(in the granular suite from #1331) now passes — byte-identical to
`node --experimental-strip-types`.
proggeramlug added a commit that referenced this pull request May 23, 2026
…1344) (#1504)

Pre-fix `process.env.X = v` hit the generic `js_object_set_field_by_name`
path and stored on a cached ProcessEnv backing object, but the read side
(`process.env.X` → `EnvGet`) consults `std::env::var` directly. The
value never round-tripped: child processes inherited the unmodified
parent env, and even `console.log(process.env.X)` immediately after the
assignment came back `undefined`.

Route `PropertySet { object: ProcessEnv, property, value }` through a
new `js_setenv(name, value)` runtime helper that:

- coerces the value to a string via `js_jsvalue_to_string` (matches
  Node's `process.env.PORT = 8080` → `"8080"` coercion),
- writes via `std::env::set_var(name, str_value)` — exactly the same
  store the existing `js_getenv` read side consults.

Add a matching `js_removeenv(name)` for the future `delete process.env.X`
hookup (HIR side is already in flight in #1331's branch).

Closes #1344.
proggeramlug added a commit that referenced this pull request May 23, 2026
…1505)

Node 20.12+ exposes `process.loadEnvFile(path?)` to read a `.env`
file from disk and add its KEY=value entries to process.env. Perry
was reading it as a bare number, so `typeof process.loadEnvFile ===
"function"` returned false and call sites crashed with "value is not
a function".

Perry today doesn't persist `process.env.X = v` writes (tracked in
#1344), so eagerly loading a `.env` would be moot. Returning
undefined is the closest no-op for the probe-and-call pattern. Real
`.env` loading is tracked separately — this PR closes the typeof /
call-doesn't-crash gap.

- Call lowering (`native_module.rs`): folds to `Expr::Undefined`,
  sitting next to the `ref` / `unref` / `setSourceMapsEnabled` /
  `getBuiltinModule` / `dlopen` / `hasUncaughtExceptionCaptureCallback`
  / `setUncaughtExceptionCaptureCallback` arms.
- Typeof fold (`lower_expr.rs`): adds `loadEnvFile` to the same
  process-method-as-function list.

Test: test-parity/node-suite/process/methods/load-env-file.ts (in
the granular suite from #1331) now passes — byte-identical to
`node --experimental-strip-types`.
proggeramlug added a commit that referenced this pull request May 23, 2026
…1478) (#1507)

Node's `performance.markResourceTiming(info, ...)` records a
PerformanceResourceTiming entry built from a PerformanceTimingInfo-
shaped object (paired with `clearResourceTimings` /
`setResourceTimingBufferSize`). Perry was reading the property as
undefined — `typeof performance.markResourceTiming === "function"`
returned false and any call crashed.

Perry doesn't track the resource-timing buffer yet, so the call is a
no-op returning undefined. The accompanying read of
`performance.getEntriesByType("resource")` still returns an empty
array, matching the no-recorded-entries shape.

- HIR dispatch (`module_static.rs`): adds `markResourceTiming` to the
  perf_hooks NativeMethodCall list.
- Codegen (`perf_hooks.rs`): folds the call to undefined, sitting
  next to the `clearResourceTimings` /
  `setResourceTimingBufferSize` arms.
- Property-read typeof (`object/native_module.rs`): adds
  `("perf_hooks", "markResourceTiming")` to the callable-export list
  so `typeof performance.markResourceTiming === "function"` matches
  Node and `const f = performance.markResourceTiming` is a callable
  bound-method closure.

Test: test-parity/node-suite/perf_hooks/resource-timing/mark-resource-timing.ts
(in the granular suite from #1331) now passes — byte-identical to
`node --experimental-strip-types`.
proggeramlug added a commit that referenced this pull request May 23, 2026
…1479) (#1516)

Node returns `"[object Performance]"` from
`Object.prototype.toString.call(performance)` — that's the spec'd
host-tag for the `Performance` interface. Perry was returning the
generic `"[object Object]"` because the perf_hooks namespace is a
`NATIVE_MODULE_CLASS_ID`-tagged ObjectHeader: it never reaches the
class-level `Symbol.toStringTag` hook (those are keyed by user-class
class_id).

- `read_native_module_name(obj_ptr)` helper in
  `object/native_module.rs` decodes the module-name string stored
  in field 0 of the namespace ObjectHeader.
- `js_object_to_string` in `object/mod.rs`: after the class-level
  hook misses, if `class_id == NATIVE_MODULE_CLASS_ID` look up the
  module name and map it via a new `native_module_to_string_tag`
  table. Today the only entry is `perf_hooks → "Performance"`;
  future modules with a defined Node tag (`url → "URL"`,
  `events → "EventEmitter"`, etc.) slot in here.

Test: test-parity/node-suite/perf_hooks/shapes/to-string-tag.ts (in
the granular suite from #1331) now passes — byte-identical to
`node --experimental-strip-types`.

`performance[Symbol.toStringTag]` reading "Performance" directly
remains a separate gap (Symbol-keyed property dispatch on namespace
objects); the spec-compliant toString path is now covered.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant