From 0422640fcd6c8b4dee327f31a3c3928e8171b01f Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Thu, 16 Jul 2026 20:54:43 +0200 Subject: [PATCH] test(inspector): expand granular parity coverage --- test-parity/node-suite/inspector/EVIDENCE.md | 47 ++++++ test-parity/node-suite/inspector/README.md | 95 ++++++++++++ .../inspector/events/console-api.ts | 28 ++++ .../inspector/events/listener-lifecycle.ts | 29 ++++ .../inspector/events/notification-order.ts | 35 +++++ .../inspector/events/script-parsed.ts | 35 +++++ .../inspector/lifecycle/endpoint.ts | 116 ++++++++++---- .../lifecycle/main-thread-connect.ts | 21 +++ .../inspector/lifecycle/method-receivers.ts | 28 ++++ .../lifecycle/open-range-validation.ts | 42 +++++ .../inspector/lifecycle/repeated-sessions.ts | 27 ++++ .../inspector/lifecycle/session-connect.ts | 23 +++ .../node-suite/inspector/network/helpers.ts | 30 ++-- .../inspector/post/callback-validation.ts | 24 +++ .../inspector/post/circular-params.ts | 17 ++ .../node-suite/inspector/post/disconnected.ts | 15 ++ .../inspector/post/method-validation.ts | 24 +++ .../node-suite/inspector/post/overloads.ts | 42 +++++ .../inspector/post/params-validation.ts | 24 +++ .../inspector/post/pending-disconnect.ts | 34 ++++ .../inspector/post/unknown-command.ts | 24 +++ .../inspector/protocol/debugger-metadata.ts | 30 ++++ .../inspector/protocol/enable-disable.ts | 21 +++ .../inspector/protocol/get-script-source.ts | 42 +++++ .../inspector/protocol/schema-domains.ts | 19 +++ .../inspector/runtime/await-promise.ts | 31 ++++ .../inspector/runtime/exception-details.ts | 34 ++++ .../runtime/get-properties-release.ts | 56 +++++++ .../inspector/runtime/numeric-specials.ts | 27 ++++ .../inspector/runtime/object-preview.ts | 33 ++++ .../inspector/runtime/primitives.ts | 29 ++++ .../inspector/runtime/release-object-group.ts | 52 +++++++ .../inspector/runtime/remote-subtypes.ts | 37 +++++ .../inspector/runtime/return-by-value.ts | 31 ++++ .../inspector/session/callback-runtime.ts | 146 ++++++++++++------ .../inspector/surface/domain-helpers.ts | 24 +++ .../inspector/surface/domain-validation.ts | 21 +++ .../node-suite/inspector/surface/exports.ts | 24 +++ .../inspector/surface/session-class.ts | 38 +++++ test-parity/node_suite_baseline.json | 14 +- 40 files changed, 1367 insertions(+), 102 deletions(-) create mode 100644 test-parity/node-suite/inspector/EVIDENCE.md create mode 100644 test-parity/node-suite/inspector/README.md create mode 100644 test-parity/node-suite/inspector/events/console-api.ts create mode 100644 test-parity/node-suite/inspector/events/listener-lifecycle.ts create mode 100644 test-parity/node-suite/inspector/events/notification-order.ts create mode 100644 test-parity/node-suite/inspector/events/script-parsed.ts create mode 100644 test-parity/node-suite/inspector/lifecycle/main-thread-connect.ts create mode 100644 test-parity/node-suite/inspector/lifecycle/method-receivers.ts create mode 100644 test-parity/node-suite/inspector/lifecycle/open-range-validation.ts create mode 100644 test-parity/node-suite/inspector/lifecycle/repeated-sessions.ts create mode 100644 test-parity/node-suite/inspector/lifecycle/session-connect.ts create mode 100644 test-parity/node-suite/inspector/post/callback-validation.ts create mode 100644 test-parity/node-suite/inspector/post/circular-params.ts create mode 100644 test-parity/node-suite/inspector/post/disconnected.ts create mode 100644 test-parity/node-suite/inspector/post/method-validation.ts create mode 100644 test-parity/node-suite/inspector/post/overloads.ts create mode 100644 test-parity/node-suite/inspector/post/params-validation.ts create mode 100644 test-parity/node-suite/inspector/post/pending-disconnect.ts create mode 100644 test-parity/node-suite/inspector/post/unknown-command.ts create mode 100644 test-parity/node-suite/inspector/protocol/debugger-metadata.ts create mode 100644 test-parity/node-suite/inspector/protocol/enable-disable.ts create mode 100644 test-parity/node-suite/inspector/protocol/get-script-source.ts create mode 100644 test-parity/node-suite/inspector/protocol/schema-domains.ts create mode 100644 test-parity/node-suite/inspector/runtime/await-promise.ts create mode 100644 test-parity/node-suite/inspector/runtime/exception-details.ts create mode 100644 test-parity/node-suite/inspector/runtime/get-properties-release.ts create mode 100644 test-parity/node-suite/inspector/runtime/numeric-specials.ts create mode 100644 test-parity/node-suite/inspector/runtime/object-preview.ts create mode 100644 test-parity/node-suite/inspector/runtime/primitives.ts create mode 100644 test-parity/node-suite/inspector/runtime/release-object-group.ts create mode 100644 test-parity/node-suite/inspector/runtime/remote-subtypes.ts create mode 100644 test-parity/node-suite/inspector/runtime/return-by-value.ts create mode 100644 test-parity/node-suite/inspector/surface/domain-helpers.ts create mode 100644 test-parity/node-suite/inspector/surface/domain-validation.ts create mode 100644 test-parity/node-suite/inspector/surface/exports.ts create mode 100644 test-parity/node-suite/inspector/surface/session-class.ts diff --git a/test-parity/node-suite/inspector/EVIDENCE.md b/test-parity/node-suite/inspector/EVIDENCE.md new file mode 100644 index 0000000000..c6f75f7dcc --- /dev/null +++ b/test-parity/node-suite/inspector/EVIDENCE.md @@ -0,0 +1,47 @@ +# `node:inspector` entry evidence + +All rows use Node 26.5.0 as oracle. `pass`, `diff`, `error`, and `timeout` +describe direct stdout/exit comparison under Deno 2.9.2 and Bun 1.3.14. Perry +classifications are recorded from two identical focused release-runner passes +(4/37, 33 diffs). Post-run generated-artifact and live-process scans found no +leaked suite process or endpoint. + +| Entry | Contract category | Node source basis | Deno | Bun | Perry | +| ------------------------------------ | ------------------------------------- | ---------------------------------------------- | ----: | ------: | ----: | +| `events/console-api.ts` | console notification shape | API events; `test-inspector-console.js` | pass | timeout | diff | +| `events/listener-lifecycle.ts` | `on`/`once`/`off` cleanup | `lib/inspector.js` message dispatch | pass | diff | diff | +| `events/notification-order.ts` | specific/generic/callback order | `lib/inspector.js` `#onMessage` | pass | timeout | diff | +| `events/script-parsed.ts` | normalized script metadata | `test-inspector-scriptparsed-context.js` | pass | error | diff | +| `lifecycle/endpoint.ts` | isolated open/dispose/reopen/close | `test-inspector-open-dispose.mjs` | diff | diff | diff | +| `lifecycle/main-thread-connect.ts` | main-thread rejection | `test-inspector-connect-to-main-thread.js` | pass | error | pass | +| `lifecycle/method-receivers.ts` | private receiver brands | `lib/inspector.js` `Session` | pass | diff | diff | +| `lifecycle/open-range-validation.ts` | port overflow validation | `test-inspector-open-port-integer-overflow.js` | pass | diff | diff | +| `lifecycle/repeated-sessions.ts` | independent in-process sessions | `test-inspector-multisession-js.js` | pass | timeout | diff | +| `lifecycle/session-connect.ts` | connect/disconnect/reconnect | `test-inspector-module.js` | pass | diff | pass | +| `network/helpers.ts` | Network function identity/descriptors | `test-inspector-emit-protocol-event.js` | diff | error | diff | +| `post/callback-validation.ts` | callback type validation | `test-inspector-module.js` | pass | error | diff | +| `post/circular-params.ts` | JSON serialization failure | `lib/inspector.js` `post()` | pass | diff | diff | +| `post/disconnected.ts` | pre/post disconnect error | `test-inspector-module.js` | pass | diff | pass | +| `post/method-validation.ts` | method type validation | `test-inspector-module.js` | pass | diff | diff | +| `post/overloads.ts` | callback second arg and null params | `lib/inspector.js` `post()` | diff | diff | diff | +| `post/params-validation.ts` | params type validation | `test-inspector-module.js` | pass | diff | diff | +| `post/pending-disconnect.ts` | pending callback completion/order | API `disconnect()` contract | error | diff | diff | +| `post/unknown-command.ts` | protocol command error | `lib/inspector.js` `#onMessage` | pass | diff | pass | +| `protocol/debugger-metadata.ts` | normalized Debugger ID result | V8 Debugger protocol | pass | error | diff | +| `protocol/enable-disable.ts` | safe domain lifecycle | API profiler examples | pass | error | diff | +| `protocol/get-script-source.ts` | controlled exact source retrieval | `test-inspector.js` | pass | error | diff | +| `protocol/schema-domains.ts` | supported-domain inventory | V8 Schema protocol | pass | error | diff | +| `runtime/await-promise.ts` | `awaitPromise` resolution | inspector API examples | pass | error | diff | +| `runtime/exception-details.ts` | thrown evaluation result | inspector exception tests | pass | error | diff | +| `runtime/get-properties-release.ts` | properties and object release | `test-inspector-bindings.js` | pass | error | diff | +| `runtime/numeric-specials.ts` | unserializable values and BigInt | V8 Runtime protocol | pass | error | diff | +| `runtime/object-preview.ts` | normalized preview properties | `test-inspector.js` | pass | error | diff | +| `runtime/primitives.ts` | primitive remote-object shapes | inspector API examples | pass | error | diff | +| `runtime/release-object-group.ts` | group release invalidation | V8 Runtime protocol | pass | error | diff | +| `runtime/remote-subtypes.ts` | stable object subtype/class metadata | V8 Runtime protocol | pass | error | diff | +| `runtime/return-by-value.ts` | object/array serialization | V8 Runtime protocol | pass | error | diff | +| `session/callback-runtime.ts` | callback lifecycle smoke | `test-inspector-module.js` | error | timeout | diff | +| `surface/domain-helpers.ts` | Network/DOMStorage/Resources exports | `lib/inspector.js` | error | error | diff | +| `surface/domain-validation.ts` | helper params validation | `test-inspector-emit-protocol-event.js` | pass | error | diff | +| `surface/exports.ts` | exact public inventory/descriptors | `lib/inspector.js` exports | diff | diff | diff | +| `surface/session-class.ts` | EventEmitter/prototype descriptors | API `Session`; `lib/inspector.js` | pass | diff | diff | diff --git a/test-parity/node-suite/inspector/README.md b/test-parity/node-suite/inspector/README.md new file mode 100644 index 0000000000..c277dac8d9 --- /dev/null +++ b/test-parity/node-suite/inspector/README.md @@ -0,0 +1,95 @@ +# `node:inspector` granular parity suite + +Deterministic callback-API coverage for Node's `node:inspector` module. Node +**26.5.0** is the oracle. Every case is a small TypeScript program whose stdout +is compared byte-for-byte with Perry; volatile protocol identifiers and engine +state are reduced to type, presence, ordering, or boolean semantic checks inside +the fixture. + +## Coverage + +- public export inventory, descriptors, domain helper inventory, `Session` + inheritance, prototype methods, and receiver behavior +- endpoint inactive/active/disposed lifecycle, range validation, main-thread + rejection, connect/double-connect/disconnect/reconnect, and independent + sessions +- `post()` method/params/callback validation, overloads, circular serialization, + connection errors, unknown commands, and pending callback completion during + disconnect +- `Runtime.enable`, + primitive/special-number/BigInt/object/exception/promise/preview result + shapes, `getProperties`, `releaseObject`, and `releaseObjectGroup` +- `Schema.getDomains`, safe Debugger/Profiler/HeapProfiler enable-disable + contracts, debugger metadata, and exact controlled script-source retrieval +- execution-context, console, and script-parsed notifications; + specific-before-generic ordering; exact `on`/`once`/`off` cleanup +- Network/DOMStorage helper inventories and validation without external + frontends + +The module is in the sequential lane in `scripts/node_suite_run.py`; no new +runner exception is needed. + +## Oracle and alternate-runtime evidence + +Primary sources audited at their exact tags: + +- Node 26.5.0: `lib/inspector.js`, `doc/api/inspector.md`, + `test/parallel/test-inspector-module.js`, `test-inspector-bindings.js`, + `test-inspector-multisession-js.js`, `test-inspector-emit-protocol-event*.js`, + `test-inspector-scriptparsed-context.js`, + `test-inspector-runtime-evaluate-with-timeout.js`, and + `test/sequential/test-inspector-open-dispose.mjs`. +- Deno 2.9.2: `ext/node/polyfills/inspector.js`, `inspector_esm.js`, + `ext/node/ops/inspector.rs`, and `tests/unit_node/inspector_test.ts`. +- Bun 1.3.14: `src/js/node/inspector.ts` and + `test/js/node/inspector/inspector.test.ts`. + +After final cleanup and endpoint subprocess isolation, three complete Node +oracle passes (111 executions) exited zero with identical aggregate stdout at +SHA-256 `fedccaf62d97bd5c1ebe1589a063e872222510b591536816ab38fe5bfb39fecd`. Deno +produced 30 exact matches, 4 diffs, and 3 errors. Bun's pinned source implements +Profiler commands while retaining stubs for the remaining endpoint surface; its +run produced 14 diffs, 19 errors, and 4 bounded timeouts. + +See [EVIDENCE.md](EVIDENCE.md) for per-entry classification. + +## Deliberate exclusions and stopping boundary + +- `node:inspector/promises` remains a separate suite; no promise-API case is + added here. +- No external DevTools/WebSocket client, fixed port, internet dependency, or + inspector frontend is used. The endpoint smoke case runs in a bounded child + process, uses loopback port `0`, closes in a `finally` path, and is forcibly + terminated by `spawnSync` on timeout. +- Worker-to-main-thread connection is limited to deterministic main-thread + rejection. Worker scheduling and cross-thread debugger traffic remain + excluded. +- `waitForDebugger()` is checked only while inactive. Its active behavior + intentionally blocks for a frontend command. +- CPU/heap profiles, sampling, coverage payloads, heap snapshots, + GC/finalization, retained-object queries, source-map/loader matrices, + breakpoints, signals, crashes, large payloads, concurrency, and stress remain + out because their useful payloads depend on timing, allocation, source + positions, or external control. +- Network and DOMStorage event delivery requiring experimental flags/frontends + is not forced into the default runner. Inventory and argument contracts remain + useful and deterministic without weakening Node assertions. +- `exceptionThrown`/`exceptionRevoked` were prototyped but not retained: + same-thread uncaught/rejected probes either terminate or do not deliver a + stable notification barrier. `Runtime.evaluate` exception details provide the + deterministic semantic invariant instead. + +The retained surface exhausts the reachable, deterministic callback contracts in +the public API and requested safe protocol subset without turning +engine-specific or timing-sensitive payloads into count-only tests. + +## `node:inspector/promises` follow-on inventory + +Node 26.5.0's `test/parallel/test-inspector-promises.js` establishes: exact +callback-module export-key parity, promise-returning `Session.post`, resolution +payload parity, and submission-order preservation when responses complete out of +order. The next isolated suite should mirror the safe lifecycle, validation, +Runtime/Schema/enable-disable, notification, and cleanup cases here using +promise settlement. The upstream CPU profile URL assertion and delayed-timer +ordering probe should be replaced by bounded deterministic semantics rather than +copied literally. diff --git a/test-parity/node-suite/inspector/events/console-api.ts b/test-parity/node-suite/inspector/events/console-api.ts new file mode 100644 index 0000000000..cc1949ee1b --- /dev/null +++ b/test-parity/node-suite/inspector/events/console-api.ts @@ -0,0 +1,28 @@ +import inspector, { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve) => + session.post("Runtime.enable", () => resolve()) + ); + const notification = new Promise((resolve) => + session.once("Runtime.consoleAPICalled", resolve) + ); + inspector.console.warn("marker", 42, true); + const { method, params } = await notification; + console.log( + "event:", + method, + params.type, + params.args.map((arg: any) => `${arg.type}:${arg.value}`).join(","), + ); + console.log( + "context:", + typeof params.executionContextId, + typeof params.timestamp, + Array.isArray(params.stackTrace.callFrames), + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/events/listener-lifecycle.ts b/test-parity/node-suite/inspector/events/listener-lifecycle.ts new file mode 100644 index 0000000000..5aab045077 --- /dev/null +++ b/test-parity/node-suite/inspector/events/listener-lifecycle.ts @@ -0,0 +1,29 @@ +import inspector, { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + let persistent = 0; + let once = 0; + const listener = () => persistent++; + session.on("Runtime.consoleAPICalled", listener); + session.once("Runtime.consoleAPICalled", () => once++); + await new Promise((resolve) => + session.post("Runtime.enable", () => resolve()) + ); + inspector.console.log(1); + inspector.console.log(2); + await new Promise((resolve) => setImmediate(resolve)); + session.off("Runtime.consoleAPICalled", listener); + inspector.console.log(3); + await new Promise((resolve) => setImmediate(resolve)); + console.log( + "counts:", + persistent, + once, + session.listenerCount("Runtime.consoleAPICalled"), + ); +} finally { + session.removeAllListeners(); + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/events/notification-order.ts b/test-parity/node-suite/inspector/events/notification-order.ts new file mode 100644 index 0000000000..040013f39d --- /dev/null +++ b/test-parity/node-suite/inspector/events/notification-order.ts @@ -0,0 +1,35 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + const order: string[] = []; + const done = new Promise((resolve) => { + session.once("Runtime.executionContextCreated", (message) => { + order.push("specific"); + console.log( + "specific shape:", + message.method, + typeof message.params.context.id, + typeof message.params.context.name, + ); + }); + const generic = (message: any) => { + if (message.method === "Runtime.executionContextCreated") { + order.push("generic"); + session.off("inspectorNotification", generic); + resolve(); + } + }; + session.on("inspectorNotification", generic); + }); + session.post("Runtime.enable", (err, value) => { + order.push("callback"); + console.log("callback:", err === null, Object.keys(value).length); + }); + await done; + await new Promise((resolve) => setImmediate(resolve)); + console.log("order:", order.join(",")); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/events/script-parsed.ts b/test-parity/node-suite/inspector/events/script-parsed.ts new file mode 100644 index 0000000000..97928fc583 --- /dev/null +++ b/test-parity/node-suite/inspector/events/script-parsed.ts @@ -0,0 +1,35 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + const scripts: any[] = []; + session.on("Debugger.scriptParsed", ({ params }) => scripts.push(params)); + await new Promise((resolve, reject) => + session.post("Debugger.enable", (err) => err ? reject(err) : resolve()) + ); + await new Promise((resolve, reject) => + session.post("Runtime.evaluate", { + expression: "1 + 2\n//# sourceURL=inspector-parity-marker.js", + }, (err) => err ? reject(err) : resolve()) + ); + const script = scripts.find((item) => + item.url === "inspector-parity-marker.js" + ); + console.log("found:", Boolean(script)); + console.log( + "shape:", + typeof script.scriptId, + script.url, + typeof script.startLine, + typeof script.startColumn, + typeof script.endLine, + typeof script.endColumn, + typeof script.executionContextId, + script.isLiveEdit, + script.sourceMapURL === "", + ); +} finally { + session.removeAllListeners(); + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/lifecycle/endpoint.ts b/test-parity/node-suite/inspector/lifecycle/endpoint.ts index d48a5c1c99..a43b5f2e53 100644 --- a/test-parity/node-suite/inspector/lifecycle/endpoint.ts +++ b/test-parity/node-suite/inspector/lifecycle/endpoint.ts @@ -1,40 +1,96 @@ +import { spawnSync } from "node:child_process"; import inspector from "node:inspector"; -function reportSync(label: string, fn: () => unknown): void { +function reportSync(label: string, fn: () => unknown): boolean { try { const value = fn(); - console.log(label, "ok", value === undefined ? "undefined" : typeof value); + console.log( + label, + "ok", + value === undefined ? "undefined" : typeof value, + ); + return true; } catch (err) { - const error = err as { constructor?: { name?: string }, code?: string, message?: string }; - console.log(label, "err", error.constructor?.name, error.code, String(error.message).split("\n")[0]); + const error = err as { + constructor?: { name?: string }; + code?: string; + message?: string; + }; + console.log( + label, + "err", + error.constructor?.name, + error.code, + String(error.message).split("\n")[0], + ); + return false; } } -console.log( - "surface:", - typeof inspector.open, - typeof inspector.close, - typeof inspector.url, - typeof inspector.waitForDebugger, - typeof inspector.console, - typeof inspector.Session, -); -console.log("url before:", inspector.url() === undefined); -reportSync("wait inactive:", () => inspector.waitForDebugger()); +if (process.env.PERRY_INSPECTOR_ENDPOINT_CHILD === "1") { + let opened = false; + try { + console.log( + "surface:", + typeof inspector.open, + typeof inspector.close, + typeof inspector.url, + typeof inspector.waitForDebugger, + typeof inspector.console, + typeof inspector.Session, + ); + console.log("url before:", inspector.url() === undefined); + console.log( + "url receiver ignored:", + Reflect.apply(inspector.url, null, [1, 2]) === undefined, + ); + console.log( + "close receiver ignored:", + Reflect.apply(inspector.close, null, [1, 2]) === undefined, + ); + reportSync("wait inactive:", () => inspector.waitForDebugger()); -const firstHandle = inspector.open(0, "127.0.0.1", false) as any; -const firstUrl = inspector.url(); -console.log("open handle:", typeof firstHandle, typeof firstHandle[Symbol.dispose]); -console.log( - "url active:", - typeof firstUrl, - /^ws:\/\/127\.0\.0\.1:\d+\/[0-9a-f-]+$/i.test(String(firstUrl)), -); -firstHandle[Symbol.dispose](); -console.log("url after dispose:", inspector.url() === undefined); + const firstHandle = inspector.open(0, "127.0.0.1", false) as any; + opened = true; + const firstUrl = inspector.url(); + console.log( + "open handle:", + typeof firstHandle, + typeof firstHandle[Symbol.dispose], + ); + console.log( + "url active:", + typeof firstUrl, + /^ws:\/\/127\.0\.0\.1:\d+\/[0-9a-f-]+$/i.test(String(firstUrl)), + ); + firstHandle[Symbol.dispose](); + opened = false; + console.log("url after dispose:", inspector.url() === undefined); -inspector.open(0, "127.0.0.1", false); -console.log("url reopened:", typeof inspector.url()); -reportSync("close:", () => inspector.close()); -console.log("url after close:", inspector.url() === undefined); -console.log("console object:", typeof inspector.console, inspector.console !== null); + inspector.open(0, "127.0.0.1", false); + opened = true; + console.log("url reopened:", typeof inspector.url()); + if (reportSync("close:", () => inspector.close())) opened = false; + console.log("url after close:", inspector.url() === undefined); + console.log( + "console object:", + typeof inspector.console, + inspector.console !== null, + ); + } finally { + if (opened) inspector.close(); + } +} else { + const child = spawnSync(process.execPath, [import.meta.filename], { + encoding: "utf8", + env: { ...process.env, PERRY_INSPECTOR_ENDPOINT_CHILD: "1" }, + timeout: 5_000, + }); + if (child.error) { + const error = child.error as { code?: string }; + console.log("child error:", error.code ?? error.constructor.name); + } else { + process.stdout.write(child.stdout); + console.log("child exit:", child.status, child.signal ?? "none"); + } +} diff --git a/test-parity/node-suite/inspector/lifecycle/main-thread-connect.ts b/test-parity/node-suite/inspector/lifecycle/main-thread-connect.ts new file mode 100644 index 0000000000..88e990b361 --- /dev/null +++ b/test-parity/node-suite/inspector/lifecycle/main-thread-connect.ts @@ -0,0 +1,21 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +try { + try { + session.connectToMainThread(); + console.log("unexpected success"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log(error.name, error.code, error.message); + } + session.connect(); + try { + session.connectToMainThread(); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log("connected:", error.name, error.code, error.message); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/lifecycle/method-receivers.ts b/test-parity/node-suite/inspector/lifecycle/method-receivers.ts new file mode 100644 index 0000000000..5665a19a40 --- /dev/null +++ b/test-parity/node-suite/inspector/lifecycle/method-receivers.ts @@ -0,0 +1,28 @@ +import { Session } from "node:inspector"; + +for ( + const name of [ + "connect", + "connectToMainThread", + "disconnect", + "post", + ] as const +) { + try { + Reflect.apply( + Session.prototype[name], + {}, + name === "post" ? ["Runtime.enable"] : [], + ); + console.log(name, "unexpected"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log( + name, + error.name, + error.code ?? "-", + error.message?.includes("private member") || + error.message === "Current thread is not a worker", + ); + } +} diff --git a/test-parity/node-suite/inspector/lifecycle/open-range-validation.ts b/test-parity/node-suite/inspector/lifecycle/open-range-validation.ts new file mode 100644 index 0000000000..ba54aa97dc --- /dev/null +++ b/test-parity/node-suite/inspector/lifecycle/open-range-validation.ts @@ -0,0 +1,42 @@ +import { spawnSync } from "node:child_process"; +import inspector from "node:inspector"; + +if (process.env.PERRY_INSPECTOR_RANGE_CHILD === "1") { + try { + for (const port of [65536, 2 ** 32 - 1]) { + try { + inspector.open(port); + console.log(port, "unexpected"); + } catch (cause) { + const error = cause as { + name?: string; + code?: string; + message?: string; + }; + console.log( + port, + error.name, + error.code, + error.message?.includes(">= 0 && <= 65535"), + ); + } finally { + inspector.close(); + } + } + } finally { + inspector.close(); + } +} else { + const child = spawnSync(process.execPath, [import.meta.filename], { + encoding: "utf8", + env: { ...process.env, PERRY_INSPECTOR_RANGE_CHILD: "1" }, + timeout: 5_000, + }); + if (child.error) { + const error = child.error as { code?: string }; + console.log("child error:", error.code ?? error.constructor.name); + } else { + process.stdout.write(child.stdout); + console.log("child exit:", child.status, child.signal ?? "none"); + } +} diff --git a/test-parity/node-suite/inspector/lifecycle/repeated-sessions.ts b/test-parity/node-suite/inspector/lifecycle/repeated-sessions.ts new file mode 100644 index 0000000000..08474ac014 --- /dev/null +++ b/test-parity/node-suite/inspector/lifecycle/repeated-sessions.ts @@ -0,0 +1,27 @@ +import { Session } from "node:inspector"; + +const first = new Session(); +const second = new Session(); +try { + first.connect(); + second.connect(); + const values: number[] = []; + await Promise.all( + [first, second].map((session, index) => + new Promise((resolve) => { + session.post("Runtime.evaluate", { + expression: `${index + 1} * 10`, + returnByValue: true, + }, (err, result) => { + console.log("callback:", index, err === null, result.result.type); + values[index] = result.result.value; + resolve(); + }); + }) + ), + ); + console.log("values:", values.join(",")); +} finally { + first.disconnect(); + second.disconnect(); +} diff --git a/test-parity/node-suite/inspector/lifecycle/session-connect.ts b/test-parity/node-suite/inspector/lifecycle/session-connect.ts new file mode 100644 index 0000000000..b1ab6d9540 --- /dev/null +++ b/test-parity/node-suite/inspector/lifecycle/session-connect.ts @@ -0,0 +1,23 @@ +import { Session } from "node:inspector"; + +function attempt(label: string, fn: () => unknown): void { + try { + console.log(label, "ok", fn() === undefined); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log(label, "err", error.name, error.code, error.message); + } +} + +const session = new Session(); +try { + attempt("disconnect fresh:", () => session.disconnect()); + attempt("connect:", () => session.connect()); + attempt("connect twice:", () => session.connect()); + attempt("disconnect:", () => session.disconnect()); + attempt("disconnect twice:", () => session.disconnect()); + attempt("reconnect:", () => session.connect()); + attempt("disconnect final:", () => session.disconnect()); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/network/helpers.ts b/test-parity/node-suite/inspector/network/helpers.ts index 621b1f57b4..70d74004d0 100644 --- a/test-parity/node-suite/inspector/network/helpers.ts +++ b/test-parity/node-suite/inspector/network/helpers.ts @@ -56,18 +56,20 @@ for (const name of methods) { const session = new inspector.Session(); session.connect(); +try { + let genericCount = 0; + let specificCount = 0; + session.on("inspectorNotification", () => { + genericCount++; + }); + session.on("Network.requestWillBeSent", () => { + specificCount++; + }); -let genericCount = 0; -let specificCount = 0; -session.on("inspectorNotification", () => { - genericCount++; -}); -session.on("Network.requestWillBeSent", () => { - specificCount++; -}); - -inspector.Network.requestWillBeSent(payload as any); -await new Promise((resolve) => setTimeout(resolve, 20)); -console.log("self session events:", genericCount, specificCount); - -session.disconnect(); + inspector.Network.requestWillBeSent(payload as any); + await Promise.resolve(); + console.log("self session events:", genericCount, specificCount); +} finally { + session.removeAllListeners(); + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/callback-validation.ts b/test-parity/node-suite/inspector/post/callback-validation.ts new file mode 100644 index 0000000000..c857679650 --- /dev/null +++ b/test-parity/node-suite/inspector/post/callback-validation.ts @@ -0,0 +1,24 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for (const value of [1, true, "callback", {}, []]) { + try { + session.post("Runtime.enable", {}, value as never); + console.log(Array.isArray(value) ? "array" : typeof value, "unexpected"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log( + Array.isArray(value) ? "array" : typeof value, + error.name, + error.code, + error.message?.startsWith( + 'The "callback" argument must be of type function.', + ), + ); + } + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/circular-params.ts b/test-parity/node-suite/inspector/post/circular-params.ts new file mode 100644 index 0000000000..47d992e872 --- /dev/null +++ b/test-parity/node-suite/inspector/post/circular-params.ts @@ -0,0 +1,17 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + const params: Record = {}; + params.self = params; + try { + session.post("Runtime.evaluate", params); + console.log("unexpected"); + } catch (cause) { + const error = cause as { name?: string; message?: string }; + console.log(error.name, error.message?.includes("circular structure")); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/disconnected.ts b/test-parity/node-suite/inspector/post/disconnected.ts new file mode 100644 index 0000000000..8cb978dd38 --- /dev/null +++ b/test-parity/node-suite/inspector/post/disconnected.ts @@ -0,0 +1,15 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +for (const label of ["before", "after"] as const) { + if (label === "after") { + session.connect(); + session.disconnect(); + } + try { + session.post("Runtime.enable"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log(label, error.name, error.code, error.message); + } +} diff --git a/test-parity/node-suite/inspector/post/method-validation.ts b/test-parity/node-suite/inspector/post/method-validation.ts new file mode 100644 index 0000000000..fa276cc5f0 --- /dev/null +++ b/test-parity/node-suite/inspector/post/method-validation.ts @@ -0,0 +1,24 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for (const value of [undefined, null, 1, true, {}, []]) { + try { + session.post(value as never); + console.log(typeof value, "unexpected"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log( + Array.isArray(value) ? "array" : typeof value, + error.name, + error.code, + error.message?.startsWith( + 'The "method" argument must be of type string.', + ), + ); + } + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/overloads.ts b/test-parity/node-suite/inspector/post/overloads.ts new file mode 100644 index 0000000000..a9c0888766 --- /dev/null +++ b/test-parity/node-suite/inspector/post/overloads.ts @@ -0,0 +1,42 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + const order: string[] = []; + await new Promise((resolve) => { + order.push("before"); + const returned = session.post("Runtime.evaluate", (err, result) => { + order.push("callback"); + const error = err as unknown as + | { code?: string; message?: string } + | null; + console.log( + "method callback:", + error?.code ?? "", + error?.message?.includes("Invalid parameters") ?? false, + result === undefined, + ); + resolve(); + }); + order.push("after"); + console.log("return:", returned === undefined); + }); + await new Promise((resolve) => { + session.post("Runtime.evaluate", null as never, (err, result) => { + const error = err as unknown as + | { code?: string; message?: string } + | null; + console.log( + "null params:", + error?.code ?? "", + error?.message?.includes("Invalid parameters") ?? false, + result === undefined, + ); + resolve(); + }); + }); + console.log("order:", order.join(",")); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/params-validation.ts b/test-parity/node-suite/inspector/post/params-validation.ts new file mode 100644 index 0000000000..02e9db0182 --- /dev/null +++ b/test-parity/node-suite/inspector/post/params-validation.ts @@ -0,0 +1,24 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for (const value of [1, true, "params", Symbol("params")]) { + try { + session.post("Runtime.enable", value as never); + console.log(typeof value, "unexpected"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log( + typeof value, + error.name, + error.code, + error.message?.startsWith( + 'The "params" argument must be of type object.', + ), + ); + } + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/pending-disconnect.ts b/test-parity/node-suite/inspector/post/pending-disconnect.ts new file mode 100644 index 0000000000..a2f42f9c00 --- /dev/null +++ b/test-parity/node-suite/inspector/post/pending-disconnect.ts @@ -0,0 +1,34 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +const order: string[] = []; +try { + const completed = new Promise((resolve) => { + session.post("Runtime.evaluate", { + expression: "new Promise(() => {})", + awaitPromise: true, + }, (cause, result) => { + order.push("callback"); + const error = cause as unknown as { + name?: string; + code?: string; + message?: string; + } | null; + console.log( + error?.name ?? "", + error?.code ?? "", + error?.message ?? "", + result === undefined, + ); + resolve(); + }); + }); + order.push("posted"); + session.disconnect(); + order.push("disconnected"); + await completed; + console.log("order:", order.join(",")); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/post/unknown-command.ts b/test-parity/node-suite/inspector/post/unknown-command.ts new file mode 100644 index 0000000000..bf5a75e996 --- /dev/null +++ b/test-parity/node-suite/inspector/post/unknown-command.ts @@ -0,0 +1,24 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve) => { + session.post("Missing.command", {}, (cause, result) => { + const error = cause as unknown as { + name?: string; + code?: string; + message?: string; + } | null; + console.log( + error?.name ?? "", + error?.code ?? "", + error?.message ?? "", + result === undefined, + ); + resolve(); + }); + }); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/protocol/debugger-metadata.ts b/test-parity/node-suite/inspector/protocol/debugger-metadata.ts new file mode 100644 index 0000000000..21f18f6f56 --- /dev/null +++ b/test-parity/node-suite/inspector/protocol/debugger-metadata.ts @@ -0,0 +1,30 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve, reject) => + session.post( + "Debugger.enable", + { maxScriptsCacheSize: 1_000_000 }, + (err, value) => { + if (err) return reject(err); + console.log( + "debugger id:", + typeof value.debuggerId, + value.debuggerId.length > 0, + Object.keys(value).sort().join(","), + ); + resolve(); + }, + ) + ); + await new Promise((resolve) => + session.post("Debugger.disable", (err, value) => { + console.log("disable:", err === null, Object.keys(value).length); + resolve(); + }) + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/protocol/enable-disable.ts b/test-parity/node-suite/inspector/protocol/enable-disable.ts new file mode 100644 index 0000000000..027b8fc361 --- /dev/null +++ b/test-parity/node-suite/inspector/protocol/enable-disable.ts @@ -0,0 +1,21 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +const post = (method: string) => + new Promise((resolve, reject) => + session.post(method, (err, value) => err ? reject(err) : resolve(value)) + ); +try { + for (const domain of ["Debugger", "Profiler", "HeapProfiler"] as const) { + const enabled = await post(`${domain}.enable`); + const disabled = await post(`${domain}.disable`); + console.log( + domain, + Object.keys(enabled).sort().join(",") || "", + Object.keys(disabled).sort().join(",") || "", + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/protocol/get-script-source.ts b/test-parity/node-suite/inspector/protocol/get-script-source.ts new file mode 100644 index 0000000000..866367fd3d --- /dev/null +++ b/test-parity/node-suite/inspector/protocol/get-script-source.ts @@ -0,0 +1,42 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + const parsed = new Promise((resolve) => { + const listener = (message: any) => { + if (message.params.url === "inspector-source-marker.js") { + session.off("Debugger.scriptParsed", listener); + resolve(message.params); + } + }; + session.on("Debugger.scriptParsed", listener); + }); + await new Promise((resolve, reject) => + session.post("Debugger.enable", (err) => err ? reject(err) : resolve()) + ); + const source = + "globalThis.__inspectorSourceMarker = 42;\n//# sourceURL=inspector-source-marker.js"; + await new Promise((resolve, reject) => + session.post( + "Runtime.evaluate", + { expression: source }, + (err) => err ? reject(err) : resolve(), + ) + ); + const { scriptId } = await parsed; + await new Promise((resolve, reject) => + session.post("Debugger.getScriptSource", { scriptId }, (err, value) => { + if (err) return reject(err); + console.log( + "source:", + value.scriptSource === source, + typeof value.bytecode === "undefined", + ); + resolve(); + }) + ); +} finally { + session.removeAllListeners(); + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/protocol/schema-domains.ts b/test-parity/node-suite/inspector/protocol/schema-domains.ts new file mode 100644 index 0000000000..b866c6ce1f --- /dev/null +++ b/test-parity/node-suite/inspector/protocol/schema-domains.ts @@ -0,0 +1,19 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve, reject) => + session.post("Schema.getDomains", (err, value) => { + if (err) return reject(err); + const domains = value.domains.map((domain: any) => + `${domain.name}@${domain.version}` + ).sort(); + console.log("count:", domains.length); + console.log("domains:", domains.join(",")); + resolve(); + }) + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/await-promise.ts b/test-parity/node-suite/inspector/runtime/await-promise.ts new file mode 100644 index 0000000000..f634f01df6 --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/await-promise.ts @@ -0,0 +1,31 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for ( + const expression of [ + "Promise.resolve(42)", + "(async () => 'ready')()", + ] as const + ) { + await new Promise((resolve, reject) => + session.post("Runtime.evaluate", { + expression, + awaitPromise: true, + returnByValue: true, + }, (err, value) => { + if (err) return reject(err); + console.log( + expression, + value.result.type, + JSON.stringify(value.result.value), + value.exceptionDetails === undefined, + ); + resolve(); + }) + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/exception-details.ts b/test-parity/node-suite/inspector/runtime/exception-details.ts new file mode 100644 index 0000000000..993b15b64d --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/exception-details.ts @@ -0,0 +1,34 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve, reject) => + session.post("Runtime.evaluate", { + expression: 'throw new TypeError("marker")', + }, (err, value) => { + if (err) return reject(err); + const { result, exceptionDetails } = value; + console.log( + "result:", + result.type, + result.subtype, + result.className, + result.description?.startsWith("TypeError: marker"), + typeof result.objectId, + ); + console.log( + "exception:", + exceptionDetails.text, + typeof exceptionDetails.exceptionId, + exceptionDetails.exception?.className, + exceptionDetails.exception?.description?.startsWith( + "TypeError: marker", + ), + ); + resolve(); + }) + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/get-properties-release.ts b/test-parity/node-suite/inspector/runtime/get-properties-release.ts new file mode 100644 index 0000000000..21d8eed442 --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/get-properties-release.ts @@ -0,0 +1,56 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +const post = (method: string, params?: object) => + new Promise((resolve, reject) => + session.post( + method, + params, + (err, value) => err ? reject(err) : resolve(value), + ) + ); +try { + const evaluated = await post("Runtime.evaluate", { + expression: "({ alpha: 1, beta: true })", + objectGroup: "fixture", + }); + const objectId = evaluated.result.objectId; + const properties = await post("Runtime.getProperties", { + objectId, + ownProperties: true, + }); + const selected = properties.result.filter((item: any) => + item.name === "alpha" || item.name === "beta" + ); + console.log( + selected.map((item: any) => + `${item.name}:${item.enumerable}:${item.configurable}:${item.value.type}:${item.value.value}` + ).join(","), + ); + console.log( + "internal arrays:", + Array.isArray(properties.internalProperties), + Array.isArray(properties.privateProperties), + ); + console.log( + "release:", + Object.keys(await post("Runtime.releaseObject", { objectId })).length, + ); + await new Promise((resolve) => + session.post("Runtime.getProperties", { objectId }, (cause) => { + const error = cause as unknown as + | { code?: string; message?: string } + | null; + console.log( + "released error:", + error?.code ?? "", + error?.message?.includes("Could not find object with given id") ?? + false, + ); + resolve(); + }) + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/numeric-specials.ts b/test-parity/node-suite/inspector/runtime/numeric-specials.ts new file mode 100644 index 0000000000..639b6e7013 --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/numeric-specials.ts @@ -0,0 +1,27 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for (const expression of ["NaN", "Infinity", "-Infinity", "-0", "123n"]) { + await new Promise((resolve, reject) => + session.post( + "Runtime.evaluate", + { expression }, + (err, { result } = {} as any) => { + if (err) return reject(err); + console.log( + expression, + result.type, + result.unserializableValue ?? "-", + result.description ?? "-", + Object.hasOwn(result, "value"), + ); + resolve(); + }, + ) + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/object-preview.ts b/test-parity/node-suite/inspector/runtime/object-preview.ts new file mode 100644 index 0000000000..b146346fb2 --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/object-preview.ts @@ -0,0 +1,33 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + await new Promise((resolve, reject) => + session.post("Runtime.evaluate", { + expression: '({ alpha: 1, beta: "two" })', + generatePreview: true, + }, (err, value) => { + if (err) return reject(err); + const result = value.result; + console.log( + "remote:", + result.type, + result.className, + result.description, + typeof result.objectId, + ); + console.log( + "preview:", + result.preview.type, + result.preview.overflow, + result.preview.properties.map((p: any) => + `${p.name}:${p.type}:${p.value}` + ).join(","), + ); + resolve(); + }) + ); +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/primitives.ts b/test-parity/node-suite/inspector/runtime/primitives.ts new file mode 100644 index 0000000000..f80aca5609 --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/primitives.ts @@ -0,0 +1,29 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +function evaluate(expression: string): Promise { + return new Promise((resolve, reject) => + session.post( + "Runtime.evaluate", + { expression, returnByValue: true }, + (err, value) => err ? reject(err) : resolve(value), + ) + ); +} +try { + for ( + const expression of ["undefined", "null", "true", "false", "42", '"hello"'] + ) { + const { result } = await evaluate(expression); + console.log( + expression, + result.type, + result.subtype ?? "-", + "value" in result ? JSON.stringify(result.value) : "", + result.description ?? "-", + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/release-object-group.ts b/test-parity/node-suite/inspector/runtime/release-object-group.ts new file mode 100644 index 0000000000..9ae01484ab --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/release-object-group.ts @@ -0,0 +1,52 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +const post = (method: string, params?: object) => + new Promise((resolve, reject) => + session.post( + method, + params, + (err, value) => err ? reject(err) : resolve(value), + ) + ); +try { + const first = await post("Runtime.evaluate", { + expression: "({ first: true })", + objectGroup: "parity-group", + }); + const second = await post("Runtime.evaluate", { + expression: "({ second: true })", + objectGroup: "parity-group", + }); + console.log( + "ids:", + typeof first.result.objectId, + typeof second.result.objectId, + first.result.objectId !== second.result.objectId, + ); + console.log( + "release group:", + Object.keys( + await post("Runtime.releaseObjectGroup", { objectGroup: "parity-group" }), + ).length, + ); + for (const objectId of [first.result.objectId, second.result.objectId]) { + await new Promise((resolve) => + session.post("Runtime.getProperties", { objectId }, (cause) => { + const error = cause as unknown as + | { code?: string; message?: string } + | null; + console.log( + "released:", + error?.code ?? "", + error?.message?.includes("Could not find object with given id") ?? + false, + ); + resolve(); + }) + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/remote-subtypes.ts b/test-parity/node-suite/inspector/runtime/remote-subtypes.ts new file mode 100644 index 0000000000..b8e7112eaf --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/remote-subtypes.ts @@ -0,0 +1,37 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for ( + const expression of [ + "[]", + "/marker/gi", + "new Date(0)", + "new Map([[1, 2]])", + "new Set([1])", + "(function named() {})", + "new Error('marker')", + ] + ) { + await new Promise((resolve, reject) => + session.post( + "Runtime.evaluate", + { expression }, + (err, { result } = {} as any) => { + if (err) return reject(err); + console.log( + expression, + result.type, + result.subtype ?? "-", + result.className ?? "-", + typeof result.objectId, + ); + resolve(); + }, + ) + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/runtime/return-by-value.ts b/test-parity/node-suite/inspector/runtime/return-by-value.ts new file mode 100644 index 0000000000..5d1dde005f --- /dev/null +++ b/test-parity/node-suite/inspector/runtime/return-by-value.ts @@ -0,0 +1,31 @@ +import { Session } from "node:inspector"; + +const session = new Session(); +session.connect(); +try { + for ( + const expression of [ + "({ answer: 42, nested: { ok: true } })", + '[1, "two", null]', + ] + ) { + await new Promise((resolve, reject) => + session.post( + "Runtime.evaluate", + { expression, returnByValue: true }, + (err, { result } = {} as any) => { + if (err) return reject(err); + console.log( + result.type, + result.subtype ?? "-", + JSON.stringify(result.value), + Object.hasOwn(result, "objectId"), + ); + resolve(); + }, + ) + ); + } +} finally { + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/session/callback-runtime.ts b/test-parity/node-suite/inspector/session/callback-runtime.ts index 839e3295b4..145fa98607 100644 --- a/test-parity/node-suite/inspector/session/callback-runtime.ts +++ b/test-parity/node-suite/inspector/session/callback-runtime.ts @@ -5,8 +5,18 @@ function reportSync(label: string, fn: () => unknown): void { const value = fn(); console.log(label, "ok", value === undefined ? "undefined" : typeof value); } catch (err) { - const error = err as { constructor?: { name?: string }, code?: string, message?: string }; - console.log(label, "err", error.constructor?.name, error.code, String(error.message).split("\n")[0]); + const error = err as { + constructor?: { name?: string }; + code?: string; + message?: string; + }; + console.log( + label, + "err", + error.constructor?.name, + error.code, + String(error.message).split("\n")[0], + ); } } @@ -21,62 +31,96 @@ console.log( typeof session.once, ); -reportSync("post before connect:", () => session.post("Runtime.evaluate", {}, () => {})); -reportSync("connectToMainThread:", () => session.connectToMainThread()); -reportSync("connect:", () => session.connect()); +reportSync( + "post before connect:", + () => session.post("Runtime.evaluate", {}, () => {}), +); -await new Promise((resolve) => { - session.post("Runtime.evaluate", { expression: "1 + 2", returnByValue: true }, (err, result) => { - console.log( - "eval callback:", - err === null, - result?.result?.type, - result?.result?.value, - result?.result?.description, +try { + reportSync("connectToMainThread:", () => session.connectToMainThread()); + reportSync("connect:", () => session.connect()); + + await new Promise((resolve) => { + session.post( + "Runtime.evaluate", + { expression: "1 + 2", returnByValue: true }, + (err, result) => { + console.log( + "eval callback:", + err === null, + result?.result?.type, + result?.result?.value, + result?.result?.description, + ); + resolve(); + }, ); - resolve(); }); -}); -await new Promise((resolve) => { - session.post("Nope.nope", {}, (err, result) => { - const error = err as { constructor?: { name?: string }, code?: string, message?: string }; - console.log( - "bad callback:", - error.constructor?.name, - error.code, - String(error.message).includes("wasn't found"), - result === undefined, - ); - resolve(); + await new Promise((resolve) => { + session.post("Nope.nope", {}, (err, result) => { + const error = err as { + constructor?: { name?: string }; + code?: string; + message?: string; + } | null; + console.log( + "bad callback:", + error?.constructor?.name ?? "", + error?.code ?? "", + String(error?.message).includes("wasn't found"), + result === undefined, + ); + resolve(); + }); }); -}); -let genericCount = 0; -let specificCount = 0; -let firstGeneric = ""; -let firstSpecific = ""; -session.on("inspectorNotification", (message: { method?: string }) => { - genericCount++; - firstGeneric ||= message?.method || ""; -}); -session.on("Runtime.consoleAPICalled", (message: { method?: string }) => { - specificCount++; - firstSpecific ||= message?.method || ""; -}); + let genericCount = 0; + let specificCount = 0; + let firstGeneric = ""; + let firstSpecific = ""; + session.on("inspectorNotification", (message: { method?: string }) => { + genericCount++; + firstGeneric ||= message?.method || ""; + }); + session.on("Runtime.consoleAPICalled", (message: { method?: string }) => { + specificCount++; + firstSpecific ||= message?.method || ""; + }); -await new Promise((resolve) => { - session.post("Runtime.enable", {}, (err, result) => { - console.log("enable callback:", err === null, Object.keys(result || {}).length); - resolve(); + await new Promise((resolve) => { + session.post("Runtime.enable", {}, (err, result) => { + console.log( + "enable callback:", + err === null, + Object.keys(result || {}).length, + ); + resolve(); + }); + }); + const notification = new Promise((resolve) => { + session.once("Runtime.consoleAPICalled", () => resolve()); }); -}); -await new Promise((resolve) => { - session.post("Runtime.evaluate", { expression: "console.log(\"callback-session-event\")" }, () => { - setTimeout(resolve, 20); + await new Promise((resolve) => { + session.post("Runtime.evaluate", { + expression: 'console.log("callback-session-event")', + }, () => resolve()); }); -}); -console.log("events:", genericCount > 0, specificCount > 0, firstGeneric, firstSpecific); + await notification; + console.log( + "events:", + genericCount > 0, + specificCount > 0, + firstGeneric, + firstSpecific, + ); -reportSync("disconnect:", () => session.disconnect()); -reportSync("post after disconnect:", () => session.post("Runtime.evaluate", {}, () => {})); + reportSync("disconnect:", () => session.disconnect()); + reportSync( + "post after disconnect:", + () => session.post("Runtime.evaluate", {}, () => {}), + ); +} finally { + session.removeAllListeners(); + session.disconnect(); +} diff --git a/test-parity/node-suite/inspector/surface/domain-helpers.ts b/test-parity/node-suite/inspector/surface/domain-helpers.ts new file mode 100644 index 0000000000..86a03bbd5f --- /dev/null +++ b/test-parity/node-suite/inspector/surface/domain-helpers.ts @@ -0,0 +1,24 @@ +import inspector from "node:inspector"; + +for (const domain of ["Network", "DOMStorage"] as const) { + const value = inspector[domain]; + console.log(domain, Object.keys(value).sort().join(",")); + for (const name of Object.keys(value).sort()) { + const fn = (value as Record)[name]; + const desc = Object.getOwnPropertyDescriptor(value, name); + console.log( + domain, + name, + fn.name, + fn.length, + desc?.enumerable, + desc?.writable, + desc?.configurable, + ); + } +} +console.log( + "resources:", + Object.keys(inspector.NetworkResources).sort().join(","), + typeof inspector.NetworkResources.put, +); diff --git a/test-parity/node-suite/inspector/surface/domain-validation.ts b/test-parity/node-suite/inspector/surface/domain-validation.ts new file mode 100644 index 0000000000..f86298ac1c --- /dev/null +++ b/test-parity/node-suite/inspector/surface/domain-validation.ts @@ -0,0 +1,21 @@ +import inspector from "node:inspector"; + +for (const domain of ["Network", "DOMStorage"] as const) { + for (const name of Object.keys(inspector[domain]).sort()) { + try { + (inspector[domain] as Record)[name]("params"); + console.log(domain, name, "unexpected"); + } catch (cause) { + const error = cause as { name?: string; code?: string; message?: string }; + console.log( + domain, + name, + error.name, + error.code, + error.message?.startsWith( + 'The "params" argument must be of type object.', + ), + ); + } + } +} diff --git a/test-parity/node-suite/inspector/surface/exports.ts b/test-parity/node-suite/inspector/surface/exports.ts new file mode 100644 index 0000000000..a9196aa0e9 --- /dev/null +++ b/test-parity/node-suite/inspector/surface/exports.ts @@ -0,0 +1,24 @@ +import inspector, * as namespace from "node:inspector"; + +const keys = Object.keys(inspector).sort(); +console.log("keys:", keys.join(",")); +console.log("default identity:", namespace.default === inspector); +for ( + const name of ["open", "close", "url", "waitForDebugger", "Session"] as const +) { + const descriptor = Object.getOwnPropertyDescriptor(inspector, name); + console.log( + name, + typeof inspector[name], + descriptor?.enumerable, + descriptor?.writable, + descriptor?.configurable, + ); +} +console.log( + "objects:", + typeof inspector.console, + typeof inspector.Network, + typeof inspector.NetworkResources, + typeof inspector.DOMStorage, +); diff --git a/test-parity/node-suite/inspector/surface/session-class.ts b/test-parity/node-suite/inspector/surface/session-class.ts new file mode 100644 index 0000000000..dd6301f137 --- /dev/null +++ b/test-parity/node-suite/inspector/surface/session-class.ts @@ -0,0 +1,38 @@ +import { EventEmitter } from "node:events"; +import { Session } from "node:inspector"; + +const session = new Session(); +console.log( + "class:", + Session.name, + Session.length, + session instanceof Session, + session instanceof EventEmitter, +); +console.log( + "prototype identity:", + Object.getPrototypeOf(session) === Session.prototype, +); +console.log( + "methods:", + Object.getOwnPropertyNames(Session.prototype).sort().join(","), +); +for ( + const name of [ + "connect", + "connectToMainThread", + "disconnect", + "post", + ] as const +) { + const fn = Session.prototype[name]; + const descriptor = Object.getOwnPropertyDescriptor(Session.prototype, name); + console.log( + name, + fn.name, + fn.length, + descriptor?.enumerable, + descriptor?.writable, + descriptor?.configurable, + ); +} diff --git a/test-parity/node_suite_baseline.json b/test-parity/node_suite_baseline.json index 15e974f4c1..795d49ca39 100644 --- a/test-parity/node_suite_baseline.json +++ b/test-parity/node_suite_baseline.json @@ -1,13 +1,13 @@ { "_schema": { "description": "Floor baseline for scripts/node_suite_regression_check.py. Each module's run must produce pass >= floor.pass; dropping below is a regression (exit 1). Improvements are always accepted and reported as ratchet candidates. Captured in the node-26 environment with scripts/node_suite_run.py (pre-warm + fast/slow lanes).", - "oracle": "node v26.3.0 on Linux (the box)", - "note": "Deterministic modules are floored at full pass. Timing/racy modules (http2, net, stream, diagnostics_channel, fs-promises) carry a small margin below observed pass so ordinary flake does not false-alarm; the guard still catches real regressions, which are large (e.g. dns 6->0, http 19->9). node_suite_run.normalize() scrubs environment-variant tokens (console.time hrtime durations, stack-trace frame lines) symmetrically before the stdout compare, so console is floored at full pass (119) on its deterministic content. http is verified 19/19 in isolation but the full-suite harness flakes to 17 under port contention, so it is floored at 17 (flake margin, not a regression); a real http break is a much larger drop. Floors refreshed from a clean node-26 run at 2810/2863 (98.1%)." + "oracle": "node v26.3.0 full-suite baseline; node v26.5.0 focused node:inspector ratchet", + "note": "Deterministic modules are floored at full pass. Timing/racy modules (http2, net, stream, diagnostics_channel, fs-promises) carry a small margin below observed pass so ordinary flake does not false-alarm; the guard still catches real regressions, which are large (e.g. dns 6->0, http 19->9). node_suite_run.normalize() scrubs environment-variant tokens (console.time hrtime durations, stack-trace frame lines) symmetrically before the stdout compare, so console is floored at full pass (119) on its deterministic content. http is verified 19/19 in isolation but the full-suite harness flakes to 17 under port contention, so it is floored at 17 (flake margin, not a regression); a real http break is a much larger drop. The last clean full-suite refresh measured 2810/2863 (98.1%); the current aggregate adds the separately measured node:inspector focused delta (+2 pass, +34 total)." }, "overall": { - "pass": 2810, - "total": 2863, - "pct": 98.1 + "pass": 2812, + "total": 2897, + "pct": 97.1 }, "modules": { "assert": { @@ -95,8 +95,8 @@ "total": 5 }, "inspector": { - "pass": 2, - "total": 3 + "pass": 4, + "total": 37 }, "inspector-promises": { "pass": 1,