Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions test-parity/node-suite/inspector/EVIDENCE.md
Original file line number Diff line number Diff line change
@@ -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 |
95 changes: 95 additions & 0 deletions test-parity/node-suite/inspector/README.md
Original file line number Diff line number Diff line change
@@ -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.
28 changes: 28 additions & 0 deletions test-parity/node-suite/inspector/events/console-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import inspector, { Session } from "node:inspector";

const session = new Session();
session.connect();
try {
await new Promise<void>((resolve) =>
session.post("Runtime.enable", () => resolve())
);
const notification = new Promise<any>((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();
}
29 changes: 29 additions & 0 deletions test-parity/node-suite/inspector/events/listener-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -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<void>((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();
}
35 changes: 35 additions & 0 deletions test-parity/node-suite/inspector/events/notification-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Session } from "node:inspector";

const session = new Session();
session.connect();
try {
const order: string[] = [];
const done = new Promise<void>((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();
}
35 changes: 35 additions & 0 deletions test-parity/node-suite/inspector/events/script-parsed.ts
Original file line number Diff line number Diff line change
@@ -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<void>((resolve, reject) =>
session.post("Debugger.enable", (err) => err ? reject(err) : resolve())
);
await new Promise<void>((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();
}
Loading
Loading