-
-
Notifications
You must be signed in to change notification settings - Fork 156
test(inspector): expand promise session parity coverage #6491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
proggeramlug
merged 2 commits into
main
from
test/expand-node-inspector-promises-parity
Jul 18, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # `node:inspector/promises` 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 come from three identical focused release-runner passes (3/30, | ||
| 27 diffs). Node was repeated five times with identical aggregate evidence. | ||
|
|
||
| | Entry | Promise contract | Node source basis | Deno | Bun | Perry | | ||
| | ----------------------------------- | ------------------------------------ | ----------------------------------- | ----: | ------: | ----: | | ||
| | `events/notification-settlement.ts` | notification before fulfillment | `inspector.js` dispatch + promisify | pass | error | diff | | ||
| | `lifecycle/disconnected.ts` | pre/post-connect async rejection | Promise `post` wrapper | pass | diff | pass | | ||
| | `lifecycle/pending-disconnect.ts` | pending-post rejection/order | `disconnect()` + promisify | error | diff | diff | | ||
| | `lifecycle/reconnect.ts` | post after reconnect | API lifecycle | pass | error | diff | | ||
| | `lifecycle/repeated-sessions.ts` | independent Promise sessions | multisession contract | pass | error | diff | | ||
| | `lifecycle/sync-control.ts` | synchronous lifecycle controls | inherited `Session` methods | pass | diff | diff | | ||
| | `post/argument-validation.ts` | validation rejects, never throws | `promisify(post)` | pass | timeout | diff | | ||
| | `post/circular-params.ts` | serialization Promise rejection | `post()` JSON dispatch | pass | diff | diff | | ||
| | `post/concurrent-order.ts` | independent/input-order settlement | upstream promises test | pass | error | diff | | ||
| | `post/concurrent-rejection.ts` | one rejection does not cancel peers | Promise post mapping | pass | error | diff | | ||
| | `post/invalid-protocol-params.ts` | protocol -32602 rejection | inspector response mapping | pass | diff | diff | | ||
| | `post/optional-params.ts` | omitted/null/undefined params | Promise post signature | pass | error | diff | | ||
| | `post/rejection-identity.ts` | stable reason identity | Promise post rejection | pass | diff | pass | | ||
| | `post/settlement-async.ts` | no same-turn settlement | promisified callback | pass | error | diff | | ||
| | `post/unknown-command.ts` | protocol -32601 rejection | inspector response mapping | pass | diff | pass | | ||
| | `protocol/enable-disable.ts` | safe empty result shapes | inspector API examples | pass | error | diff | | ||
| | `protocol/schema-domains.ts` | Schema Promise payload | V8 Schema protocol | pass | error | diff | | ||
| | `runtime/await-promise.ts` | fulfilled awaited evaluation | inspector API examples | pass | error | diff | | ||
| | `runtime/await-rejection.ts` | rejection resolves exception details | V8 Runtime protocol | pass | error | diff | | ||
| | `runtime/exception-details.ts` | throw resolves exception details | V8 Runtime protocol | pass | error | diff | | ||
| | `runtime/get-properties-release.ts` | object lifecycle/rejection | V8 Runtime protocol | pass | error | diff | | ||
| | `runtime/numeric-specials.ts` | special/BigInt result shapes | V8 Runtime protocol | pass | error | diff | | ||
| | `runtime/primitives.ts` | primitive result shapes | inspector docs | pass | error | diff | | ||
| | `runtime/return-by-value.ts` | object/array by-value results | V8 Runtime protocol | pass | error | diff | | ||
| | `surface/constructor.ts` | constructor call/extra argument | subclass definition | pass | error | diff | | ||
| | `surface/exports.ts` | keys/descriptors/import identity | module object spread | diff | diff | diff | | ||
| | `surface/inherited-receivers.ts` | synchronous receiver brands | inherited `Session` methods | pass | diff | diff | | ||
| | `surface/post-descriptor.ts` | promisified method descriptor | `promisify(post)` assignment | pass | diff | diff | | ||
| | `surface/post-receiver.ts` | receiver error becomes rejection | `promisify(post)` | pass | diff | diff | | ||
| | `surface/session-class.ts` | subclass/prototype/method identity | Promise Session definition | pass | pass | diff | | ||
|
|
||
| No Node fixture failed or timed out. Perry produced no compile failures or | ||
| execution timeouts. After the alternate-runtime timeout and all focused runs, | ||
| process, endpoint, and generated-artifact scans found no live suite process, | ||
| listening inspector endpoint, or repository artifact. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # `node:inspector/promises` granular parity suite | ||
|
|
||
| Deterministic Promise-API coverage for Node's `node:inspector/promises` module. | ||
| Node **26.5.0** is the oracle. Each fixture isolates one public Promise contract | ||
| and prints only stable semantic fields; protocol IDs, contexts, scripts, stacks, | ||
| locations, timestamps, profiles, and engine text are not compared. | ||
|
|
||
| ## Coverage | ||
|
|
||
| - exact callback-module export-key parity, descriptors, import identities, the | ||
| distinct `Session` subclass, prototype chains, constructor behavior, inherited | ||
| control methods, and Promise `post()` receiver behavior | ||
| - synchronous `connect()`/`connectToMainThread()`/`disconnect()` control versus | ||
| asynchronous `post()` validation and connection rejection | ||
| - reconnect, independent sessions, pending-post disconnect, same-turn | ||
| non-settlement, stable rejection identity, and independent concurrent posts | ||
| - controlled out-of-order completion with a resolver barrier, proving both | ||
| independent settlement and `Promise.all()` input ordering without timers | ||
| - deterministic `Runtime`, `Schema`, Debugger, Profiler, and HeapProfiler | ||
| resolution/rejection shapes, including `getProperties`/`releaseObject` | ||
| - primitive, special-number, BigInt, by-value, fulfilled `awaitPromise`, thrown | ||
| evaluation, and rejected `awaitPromise` results | ||
| - the one Promise-distinct notification contract: specific and generic events | ||
| are delivered before the enabling `post()` Promise fulfills | ||
|
|
||
| The module already runs in the sequential lane in `scripts/node_suite_run.py`. | ||
|
|
||
| ## Oracle and alternate-runtime evidence | ||
|
|
||
| Primary sources audited at exact tags: | ||
|
|
||
| - Node 26.5.0: `lib/inspector/promises.js`, `lib/inspector.js`, | ||
| `doc/api/inspector.md`, and `test/parallel/test-inspector-promises.js`. | ||
| - Deno 2.9.2: `ext/node/polyfills/inspector/promises.js`, `promises_esm.js`, the | ||
| callback implementation, and `tests/unit_node/inspector_test.ts`. | ||
| - Bun 1.3.14: `src/js/node/inspector.promises.ts`, `src/js/node/inspector.ts`, | ||
| and `test/js/node/inspector/inspector.test.ts`. | ||
|
|
||
| Five complete Node oracle passes (150 executions) exited zero with identical | ||
| aggregate stdout/exit data at SHA-256 | ||
| `994cab8da5deb3aa67bb505560eae5f6d7ee0754dfdd0aad92f4c95266688d49`. Deno | ||
| produced 28 exact matches, one surface diff, and one deterministic process | ||
| error. Bun produced one exact match, 11 diffs, 17 errors, and one bounded | ||
| timeout. Three focused release-runner passes classified Perry identically at | ||
| **3/30**, with 27 stable output/exit diffs and no compile failure, runtime | ||
| timeout, or crash. | ||
|
|
||
| Deno mirrors Node's subclass-plus-promisify design but omits `NetworkResources`. | ||
| Its pending-disconnect case reproducibly aborts with a V8 evaluate-callback | ||
| assertion instead of rejecting the Promise. Bun implements a Promise subclass | ||
| around its callback inspector, but only Profiler commands have a backend; | ||
| Runtime and Schema commands reject, and several validation/lifecycle semantics | ||
| differ from Node. Node remains the oracle for both divergences. | ||
|
|
||
| See [EVIDENCE.md](EVIDENCE.md) for per-entry classification. | ||
|
|
||
| ## Upstream and callback-suite reconciliation | ||
|
|
||
| Node's exact `test-inspector-promises.js` inventory is reconciled as follows: | ||
|
|
||
| 1. callback-module export keys: `surface/exports.ts`; | ||
| 2. `Session.post()` returns a Promise: receiver, validation, disconnected, and | ||
| settlement fixtures; | ||
| 3. resolved protocol payloads: safe Runtime/Schema/enable-disable fixtures; | ||
| 4. submission/input order with a slow response: `post/concurrent-order.ts`, | ||
| using an explicit resolver barrier rather than the upstream 100 ms timer; | ||
| 5. CPU-profile URL payload: excluded because sampling/profile contents and | ||
| source URLs are volatile; safe empty results and Runtime values prove | ||
| resolution. | ||
|
|
||
| The 37 callback fixtures from PR #6490 are also accounted for: | ||
|
|
||
| - Promise-specific counterparts retained: `lifecycle/main-thread-connect`, | ||
| `method-receivers`, `repeated-sessions`, `session-connect`; | ||
| `post/circular-params`, `disconnected`, `method-validation`, | ||
| `params-validation`, `pending-disconnect`, `unknown-command`; | ||
| `protocol/enable-disable`, `schema-domains`; `runtime/await-promise`, | ||
| `exception-details`, `get-properties-release`, `numeric-specials`, | ||
| `primitives`, `return-by-value`; `events/notification-order`; and | ||
| `surface/exports`, `session-class`. | ||
| - Callback-only and non-applicable: `post/callback-validation`, `overloads`, and | ||
| `session/callback-runtime`. | ||
| - Inherited or protocol-payload duplicates deliberately not copied: | ||
| `events/console-api`, `listener-lifecycle`, `script-parsed`; | ||
| `lifecycle/endpoint`, `open-range-validation`; `network/helpers`; | ||
| `protocol/debugger-metadata`, `get-script-source`; `runtime/object-preview`, | ||
| `release-object-group`, `remote-subtypes`; and `surface/domain-helpers`, | ||
| `domain-validation`. | ||
|
|
||
| ## Deliberate exclusions and stopping boundary | ||
|
|
||
| - Endpoint `open`/`close`/`url`, range checks, domain helpers, and callback | ||
| event inventories are shared identities/inherited behavior already proven by | ||
| PR #6490; only their Promise-module import identity is retained here. | ||
| - No fixed port, external DevTools/WebSocket client, frontend, internet, worker | ||
| race, active `waitForDebugger`, breakpoint, signal, crash, large payload, or | ||
| stress case is used. | ||
| - CPU/heap profiles, sampling, coverage payloads, heap snapshots, | ||
| GC/finalization, `queryObjects` retention, memory pressure, and | ||
| source/position payloads remain excluded as timing-, allocation-, or | ||
| engine-dependent. | ||
| - An explicit third callback is outside the Promise signature and can strand the | ||
| promisified Promise forever; it is documented rather than tested. | ||
| - Every rejection is awaited/caught. Every connected session disconnects in a | ||
| `finally` path, every installed listener is removed, and the controlled global | ||
| resolver is deleted. The only derived Promise observers include rejection | ||
| handlers or are themselves awaited. | ||
|
|
||
| This exhausts the reachable deterministic Promise-specific surface in Node | ||
| 26.5.0 and the requested safe protocol subset without count-driven assertions, | ||
| arbitrary sleeps, or duplicated callback contracts. |
31 changes: 31 additions & 0 deletions
31
test-parity/node-suite/inspector-promises/events/notification-settlement.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| const order: string[] = []; | ||
| const specific = () => order.push("specific"); | ||
| const generic = (message: { method?: string }) => { | ||
| if (message.method === "Runtime.executionContextCreated") { | ||
| order.push("generic"); | ||
| } | ||
| }; | ||
| session.on("Runtime.executionContextCreated", specific); | ||
| session.on("inspectorNotification", generic); | ||
| session.connect(); | ||
| try { | ||
| const pending = session.post("Runtime.enable"); | ||
| const observed = pending.then((value) => { | ||
| order.push("promise"); | ||
| return value; | ||
| }); | ||
| await observed; | ||
| console.log("order:", order.join(",")); | ||
| console.log( | ||
| "listeners:", | ||
| session.listenerCount("Runtime.executionContextCreated"), | ||
| session.listenerCount("inspectorNotification"), | ||
| ); | ||
| } finally { | ||
| session.off("Runtime.executionContextCreated", specific); | ||
| session.off("inspectorNotification", generic); | ||
| session.disconnect(); | ||
| } |
32 changes: 32 additions & 0 deletions
32
test-parity/node-suite/inspector-promises/lifecycle/disconnected.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| for (const label of ["before", "after"] as const) { | ||
| if (label === "after") { | ||
| session.connect(); | ||
| session.disconnect(); | ||
| } | ||
| let pending: Promise<unknown> | undefined; | ||
| let synchronous = false; | ||
| try { | ||
| try { | ||
| pending = session.post("Runtime.enable"); | ||
| } catch (inner) { | ||
| // synchronous throw: record it and re-throw so the real error reaches the | ||
| // outer catch instead of being swallowed with pending undefined. | ||
| synchronous = true; | ||
| throw inner; | ||
| } | ||
| await pending; | ||
| console.log(label, "unexpected"); | ||
| } catch (error) { | ||
| const cause = error as { name?: string; code?: string }; | ||
| console.log( | ||
| label, | ||
| synchronous, | ||
| pending instanceof Promise, | ||
| cause.name, | ||
| cause.code, | ||
| ); | ||
| } | ||
| } |
31 changes: 31 additions & 0 deletions
31
test-parity/node-suite/inspector-promises/lifecycle/pending-disconnect.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| session.connect(); | ||
| const order: string[] = []; | ||
| try { | ||
| const pending = session.post("Runtime.evaluate", { | ||
| expression: "new Promise(() => {})", | ||
| awaitPromise: true, | ||
| }); | ||
| order.push("posted"); | ||
| session.disconnect(); | ||
| order.push("disconnected"); | ||
| try { | ||
| await pending; | ||
| console.log("unexpected resolution"); | ||
| } catch (error) { | ||
| order.push("rejected"); | ||
| const cause = error as { name?: string; code?: string; message?: string }; | ||
| console.log( | ||
| "interrupted:", | ||
| cause.name, | ||
| cause.code, | ||
| cause.message?.includes("-32000"), | ||
| cause.message?.includes("Execution context was destroyed"), | ||
| ); | ||
| } | ||
| console.log("order:", order.join(",")); | ||
| } finally { | ||
| session.disconnect(); | ||
| } |
15 changes: 15 additions & 0 deletions
15
test-parity/node-suite/inspector-promises/lifecycle/reconnect.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| try { | ||
| session.connect(); | ||
| const first = await session.post("Runtime.evaluate", { expression: "6 * 7" }); | ||
| session.disconnect(); | ||
| session.connect(); | ||
| const second = await session.post("Runtime.evaluate", { | ||
| expression: "7 * 8", | ||
| }); | ||
| console.log("values:", first.result.value, second.result.value); | ||
| } finally { | ||
| session.disconnect(); | ||
| } |
16 changes: 16 additions & 0 deletions
16
test-parity/node-suite/inspector-promises/lifecycle/repeated-sessions.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const first = new Session(); | ||
| const second = new Session(); | ||
| first.connect(); | ||
| second.connect(); | ||
| try { | ||
| const [one, two] = await Promise.all([ | ||
| first.post("Runtime.evaluate", { expression: "20 + 1" }), | ||
| second.post("Runtime.evaluate", { expression: "20 + 2" }), | ||
| ]); | ||
| console.log("values:", one.result.value, two.result.value); | ||
| } finally { | ||
| first.disconnect(); | ||
| second.disconnect(); | ||
| } |
35 changes: 35 additions & 0 deletions
35
test-parity/node-suite/inspector-promises/lifecycle/sync-control.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| try { | ||
| const connected = session.connect(); | ||
| console.log( | ||
| "connect:", | ||
| connected === undefined, | ||
| connected instanceof Promise, | ||
| ); | ||
| try { | ||
| session.connect(); | ||
| console.log("unexpected second connect"); | ||
| } catch (error) { | ||
| const cause = error as { name?: string; code?: string }; | ||
| console.log("second connect:", cause.name, cause.code); | ||
| } | ||
| const disconnected = session.disconnect(); | ||
| const repeated = session.disconnect(); | ||
| console.log( | ||
| "disconnect:", | ||
| disconnected === undefined, | ||
| disconnected instanceof Promise, | ||
| repeated === undefined, | ||
| ); | ||
| try { | ||
| session.connectToMainThread(); | ||
| console.log("unexpected main-thread connection"); | ||
| } catch (error) { | ||
| const cause = error as { name?: string; code?: string }; | ||
| console.log("main thread:", cause.name, cause.code); | ||
| } | ||
| } finally { | ||
| session.disconnect(); | ||
| } |
43 changes: 43 additions & 0 deletions
43
test-parity/node-suite/inspector-promises/post/argument-validation.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { Session } from "node:inspector/promises"; | ||
|
|
||
| const session = new Session(); | ||
| session.connect(); | ||
| try { | ||
| const cases: Array<[string, () => Promise<unknown>]> = [ | ||
| ["missing", () => session.post()], | ||
| ["number method", () => session.post(1 as never)], | ||
| ["number params", () => session.post("Runtime.enable", 1 as never)], | ||
| ["string params", () => session.post("Runtime.enable", "x" as never)], | ||
| [ | ||
| "function params", | ||
| () => session.post("Runtime.enable", (() => {}) as never), | ||
| ], | ||
| ]; | ||
| for (const [label, run] of cases) { | ||
| let pending: Promise<unknown> | undefined; | ||
| let synchronous = false; | ||
| try { | ||
| try { | ||
| pending = run(); | ||
| } catch (inner) { | ||
| // synchronous throw: record it and re-throw so the real error (name/code) | ||
| // reaches the outer catch instead of being swallowed with pending undefined. | ||
| synchronous = true; | ||
| throw inner; | ||
| } | ||
| await pending; | ||
| console.log(label, "unexpected", synchronous); | ||
| } catch (error) { | ||
| const cause = error as { name?: string; code?: string }; | ||
| console.log( | ||
| label, | ||
| synchronous, | ||
| pending instanceof Promise, | ||
| cause.name, | ||
| cause.code, | ||
| ); | ||
| } | ||
| } | ||
| } finally { | ||
| session.disconnect(); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.