diff --git a/scripts/node_suite_run.py b/scripts/node_suite_run.py index 60c6e8da44..83f8555ffc 100644 --- a/scripts/node_suite_run.py +++ b/scripts/node_suite_run.py @@ -82,6 +82,7 @@ def normalize(text: str) -> str: "http", "http2", "https", "net", "dgram", "tls", "cluster", "dns", "async_hooks", "stream", "child_process", "worker_threads", "inspector", "inspector-promises", "repl", "diagnostics_channel", "timers", "perf_hooks", "fetch", + "v8", "trace_events", } diff --git a/test-parity/node-suite/v8/README.md b/test-parity/node-suite/v8/README.md new file mode 100644 index 0000000000..6d6b25076e --- /dev/null +++ b/test-parity/node-suite/v8/README.md @@ -0,0 +1,113 @@ +# node:v8 granular parity suite + +Deterministic Node.js compatibility coverage for Perry's `node:v8` surface. The +fixtures compare semantic contracts rather than V8-internal byte streams, +volatile heap sizes, engine addresses, stacks, timestamps, or GC timing. + +## Pinned primary evidence + +Node 26.5.0 is the oracle. Its tag resolves to commit +[`bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb`](https://github.com/nodejs/node/tree/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb). +The audit used: + +- [`lib/v8.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/lib/v8.js) +- [`doc/api/v8.md`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/doc/api/v8.md) +- [`test-v8-serdes.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/test/parallel/test-v8-serdes.js) +- [`test-v8-stats.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/test/parallel/test-v8-stats.js) +- [`test-v8-version-tag.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/test/parallel/test-v8-version-tag.js) +- [`test-v8-flag-type-check.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/test/parallel/test-v8-flag-type-check.js) +- [`test-v8-getheapsnapshot-twice.js`](https://github.com/nodejs/node/blob/bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb/test/parallel/test-v8-getheapsnapshot-twice.js) +- the `test-promise-hook-*` and `test-v8-startup-snapshot-api.js` families at + the same commit + +The cross-runtime audit used Deno 2.9.3 stable commit +[`f39575ecd50602a5b42b1ba8e93849460de9fcf4`](https://github.com/denoland/deno/blob/f39575ecd50602a5b42b1ba8e93849460de9fcf4/ext/node/polyfills/v8.ts) +and current source commit +[`803a3c933e1e23e0972445293ec0b34b8da96ccc`](https://github.com/denoland/deno/blob/803a3c933e1e23e0972445293ec0b34b8da96ccc/ext/node/polyfills/v8.ts), +plus Bun 1.3.14 stable commit +[`0d9b296af33f2b851fcbf4df3e9ec89751734ba4`](https://github.com/oven-sh/bun/blob/0d9b296af33f2b851fcbf4df3e9ec89751734ba4/src/js/node/v8.ts) +and current source commit +[`aca54d5c2b874ac304a3bbe1d67630e4daf17b43`](https://github.com/oven-sh/bun/blob/aca54d5c2b874ac304a3bbe1d67630e4daf17b43/src/js/node/v8.ts). + +## Coverage (46 fixtures) + +- **Exports and classes (8):** exact ESM surface, descriptors, import identity, + constructor/prototype metadata, inheritance, instance identity, and receiver + validation. +- **Structured clone helpers (12):** primitives and special numbers, sparse + arrays, objects, Map/Set identity, typed arrays, Buffer/DataView/ArrayBuffer, + Date/RegExp, Error cause, Float16Array, shared/cyclic graphs, corrupt wire + data, invalid sources, and invalid values. +- **Serializer classes (8):** headers and sequential values, raw integer/double + methods, raw Buffer/TypedArray/DataView input, release reuse, wire version, + ArrayBuffer transfer identity, validation, and every Node 26 ArrayBufferView + input family. +- **Heap diagnostics (7):** stable heap/code/space/C++ statistic shapes and + types, parsed heap-snapshot schema, two consecutive streams, isolated file + output, and cleanup. +- **Promise hooks (5):** namespace metadata, validation, stopper behavior, + parent identity, and filtered init/before/settled/after ordering. +- **GCProfiler (2):** lifecycle/report shape and `Symbol.dispose` behavior. +- **Version flags (2):** repeatable uint32 tag and isolated flag/type behavior. +- **String representation (1):** stable Latin-1/BMP/astral cases and validation. +- **Startup snapshots (1):** ordinary-process surface and + `ERR_NOT_BUILDING_SNAPSHOT` boundary. + +The module runs in the sequential lane because heap snapshots are native, +memory-heavy work and `setFlagsFromString()` mutates process-global V8 state. +Every parity fixture still executes in a fresh process. + +## Cross-runtime divergences + +- Deno backs the serializer classes with V8 and rewrites wire version 16 to 15 + for Node interoperability. It omits several modern exports and its + `startupSnapshot` callbacks are surface-only rather than Node's ordinary-mode + errors. +- Bun uses JavaScriptCore serialization, so its wire bytes intentionally do not + interoperate with Node/Deno. Most serializer lifecycle, promise-hook, flag, + and profiler APIs are stubs. Bun's heap statistics are explicitly approximate. +- Node remains the assertion oracle. These engine-design differences are + documented rather than hidden by weakening Node contracts. + +## Deliberate stopping boundary + +Excluded from this deterministic lane: + +- exact serialized bytes or cross-engine wire compatibility; +- Node-internal host objects and private `_writeHostObject`, `_readHostObject`, + or `_getDataCloneError` overrides; these require internal bindings and are not + public `node:v8` contracts; +- pinned legacy wire blobs and unaligned legacy typed-array bytes, which test + exact engine wire compatibility rather than semantic round trips; +- exact heap sizes, addresses, space population, GC/finalization timing, + retention and `queryObjects()` counts; +- raw CPU/heap profile and coverage payloads, inspector coupling, code-cache + bytes, memory pressure, leak/stress, crash/fault injection, and concurrency; +- `setHeapSnapshotNearHeapLimit()` and other near-OOM behavior; +- startup snapshot building, which requires process flags and generated + artifacts. Ordinary-process behavior is already exhausted by the focused + startup fixture. + +Heap snapshot fixtures parse only stable schema relationships, consume or +destroy streams, write inside an isolated temporary directory, and remove all +files. The valid flag fixture is process-isolated and cannot leak into another +fixture. + +## Measured Perry classification + +Repeated Node 26.5.0 oracle runs produced identical output for all 46 fixtures. +Repeated focused Perry differential runs produced the same **14/46** result: 14 +passes, 30 output mismatches, one stable SIGSEGV (`serialize/builtins` after +printing its semantic observations), one stable timeout +(`serialize/references-cycles`), and zero compile failures. The compact runner +classifies the signal mismatch in its `diff` bucket, yielding +`pass=14, diff=31, perry_err=1`. + +Validation also includes Node syntax checks for every fixture, +`deno fmt +--check`, `cargo fmt --all -- --check`, the Rust file-size gate, JSON +parsing and baseline/count invariants, release builds for the compiler/runtime +packages, `git diff --check`, and staged-scope/artifact checks. + +These failures are intentional parity targets. This tests-only suite does not +change compiler, linker, runtime, or build behavior to conceal them. diff --git a/test-parity/node-suite/v8/classes/constructor-validation.ts b/test-parity/node-suite/v8/classes/constructor-validation.ts new file mode 100644 index 0000000000..bab23e8a29 --- /dev/null +++ b/test-parity/node-suite/v8/classes/constructor-validation.ts @@ -0,0 +1,32 @@ +import * as v8 from "node:v8"; + +for ( + const name of [ + "Serializer", + "Deserializer", + "DefaultSerializer", + "DefaultDeserializer", + "GCProfiler", + ] as const +) { + try { + (v8[name] as any)(); + console.log(name + ": no throw"); + } catch (error: any) { + console.log(name + ":", error.name, error.code); + } +} + +for ( + const [label, value] of [["undefined", undefined], ["string", "bad"], [ + "object", + {}, + ]] as const +) { + try { + new v8.Deserializer(value as any); + console.log("Deserializer " + label + ": no throw"); + } catch (error: any) { + console.log("Deserializer " + label + ":", error.name, error.code); + } +} diff --git a/test-parity/node-suite/v8/classes/instances-inheritance.ts b/test-parity/node-suite/v8/classes/instances-inheritance.ts new file mode 100644 index 0000000000..37955da85c --- /dev/null +++ b/test-parity/node-suite/v8/classes/instances-inheritance.ts @@ -0,0 +1,25 @@ +import * as v8 from "node:v8"; + +const serializer = new v8.DefaultSerializer(); +const serialized = v8.serialize({ ok: true }); +const deserializer = new v8.DefaultDeserializer(serialized); +const profiler = new v8.GCProfiler(); + +console.log( + "serializer:", + serializer instanceof v8.DefaultSerializer, + serializer instanceof v8.Serializer, +); +console.log( + "deserializer:", + deserializer instanceof v8.DefaultDeserializer, + deserializer instanceof v8.Deserializer, +); +console.log("profiler:", profiler instanceof v8.GCProfiler); +console.log( + "constructors:", + serializer.constructor.name, + deserializer.constructor.name, + profiler.constructor.name, +); +console.log("independent:", new v8.Serializer() !== new v8.Serializer()); diff --git a/test-parity/node-suite/v8/classes/prototypes.ts b/test-parity/node-suite/v8/classes/prototypes.ts new file mode 100644 index 0000000000..6e1af74675 --- /dev/null +++ b/test-parity/node-suite/v8/classes/prototypes.ts @@ -0,0 +1,36 @@ +import * as v8 from "node:v8"; + +for ( + const name of [ + "Serializer", + "Deserializer", + "DefaultSerializer", + "DefaultDeserializer", + "GCProfiler", + ] as const +) { + const Constructor = v8[name]; + console.log(name, typeof Constructor, Constructor.length, Constructor.name); + console.log( + name + " prototype:", + Object.getOwnPropertyNames(Constructor.prototype).join(","), + ); + const descriptor = Object.getOwnPropertyDescriptor(Constructor, "prototype")!; + console.log( + name + " prototype descriptor:", + descriptor.writable, + descriptor.enumerable, + descriptor.configurable, + ); +} + +console.log( + "serializer inheritance:", + Object.getPrototypeOf(v8.DefaultSerializer.prototype) === + v8.Serializer.prototype, +); +console.log( + "deserializer inheritance:", + Object.getPrototypeOf(v8.DefaultDeserializer.prototype) === + v8.Deserializer.prototype, +); diff --git a/test-parity/node-suite/v8/classes/receiver-validation.ts b/test-parity/node-suite/v8/classes/receiver-validation.ts new file mode 100644 index 0000000000..e12a965209 --- /dev/null +++ b/test-parity/node-suite/v8/classes/receiver-validation.ts @@ -0,0 +1,28 @@ +import * as v8 from "node:v8"; + +for ( + const [label, fn] of [ + ["Serializer.writeHeader", v8.Serializer.prototype.writeHeader], + ["Serializer.releaseBuffer", v8.Serializer.prototype.releaseBuffer], + ["Deserializer.readHeader", v8.Deserializer.prototype.readHeader], + ["GCProfiler.start", v8.GCProfiler.prototype.start], + ] as const +) { + for ( + const [receiverLabel, receiver] of [["undefined", undefined], [ + "object", + {}, + ], ["null", null]] as const + ) { + try { + fn.call(receiver); + console.log(label + " " + receiverLabel + ": no throw"); + } catch (error: any) { + console.log( + label + " " + receiverLabel + ":", + error.name, + error.code ?? "no-code", + ); + } + } +} diff --git a/test-parity/node-suite/v8/exports/descriptors.ts b/test-parity/node-suite/v8/exports/descriptors.ts new file mode 100644 index 0000000000..5b593b56b5 --- /dev/null +++ b/test-parity/node-suite/v8/exports/descriptors.ts @@ -0,0 +1,23 @@ +import * as v8 from "node:v8"; + +for ( + const key of [ + "serialize", + "Serializer", + "promiseHooks", + "startupSnapshot", + "GCProfiler", + ] as const +) { + const descriptor = Object.getOwnPropertyDescriptor(v8, key)!; + console.log( + key, + descriptor.enumerable, + descriptor.configurable, + "writable" in descriptor ? descriptor.writable : "accessor", + typeof descriptor.value, + ); +} + +console.log("tag:", Object.prototype.toString.call(v8)); +console.log("extensible:", Object.isExtensible(v8)); diff --git a/test-parity/node-suite/v8/exports/diagnostics-surface.ts b/test-parity/node-suite/v8/exports/diagnostics-surface.ts index 2b3d67c167..15448464f7 100644 --- a/test-parity/node-suite/v8/exports/diagnostics-surface.ts +++ b/test-parity/node-suite/v8/exports/diagnostics-surface.ts @@ -9,13 +9,15 @@ import { writeHeapSnapshot, } from "node:v8"; -for (const [name, value] of [ - ["getCppHeapStatistics", getCppHeapStatistics], - ["getHeapSnapshot", getHeapSnapshot], - ["isStringOneByteRepresentation", isStringOneByteRepresentation], - ["queryObjects", queryObjects], - ["startCpuProfile", startCpuProfile], - ["writeHeapSnapshot", writeHeapSnapshot], -] as const) { +for ( + const [name, value] of [ + ["getCppHeapStatistics", getCppHeapStatistics], + ["getHeapSnapshot", getHeapSnapshot], + ["isStringOneByteRepresentation", isStringOneByteRepresentation], + ["queryObjects", queryObjects], + ["startCpuProfile", startCpuProfile], + ["writeHeapSnapshot", writeHeapSnapshot], + ] as const +) { console.log(name, typeof value, value.length, typeof (v8 as any)[name]); } diff --git a/test-parity/node-suite/v8/exports/import-identity.ts b/test-parity/node-suite/v8/exports/import-identity.ts new file mode 100644 index 0000000000..f3035add87 --- /dev/null +++ b/test-parity/node-suite/v8/exports/import-identity.ts @@ -0,0 +1,15 @@ +import * as prefixed from "node:v8"; +import legacy from "v8"; +import { deserialize, serialize } from "node:v8"; + +console.log("module identity:", prefixed.serialize === legacy.serialize); +console.log( + "named identity:", + serialize === prefixed.serialize, + deserialize === prefixed.deserialize, +); +console.log( + "default keys:", + Object.keys(legacy).length === Object.keys(prefixed).length, +); +console.log("roundtrip:", deserialize(serialize("identity"))); diff --git a/test-parity/node-suite/v8/exports/public-surface.ts b/test-parity/node-suite/v8/exports/public-surface.ts new file mode 100644 index 0000000000..670aae785a --- /dev/null +++ b/test-parity/node-suite/v8/exports/public-surface.ts @@ -0,0 +1,33 @@ +import * as v8 from "node:v8"; + +const expected = [ + "DefaultDeserializer", + "DefaultSerializer", + "Deserializer", + "GCProfiler", + "Serializer", + "cachedDataVersionTag", + "default", + "deserialize", + "getCppHeapStatistics", + "getHeapCodeStatistics", + "getHeapSnapshot", + "getHeapSpaceStatistics", + "getHeapStatistics", + "isStringOneByteRepresentation", + "promiseHooks", + "queryObjects", + "serialize", + "setFlagsFromString", + "setHeapSnapshotNearHeapLimit", + "startCpuProfile", + "startHeapProfile", + "startupSnapshot", + "stopCoverage", + "takeCoverage", + "writeHeapSnapshot", +]; + +console.log("keys:", Object.keys(v8).join(",")); +console.log("exact:", expected.every((key, i) => Object.keys(v8)[i] === key)); +console.log("count:", Object.keys(v8).length); diff --git a/test-parity/node-suite/v8/gc-profiler/dispose.ts b/test-parity/node-suite/v8/gc-profiler/dispose.ts new file mode 100644 index 0000000000..84a2fb5777 --- /dev/null +++ b/test-parity/node-suite/v8/gc-profiler/dispose.ts @@ -0,0 +1,13 @@ +import { GCProfiler } from "node:v8"; + +const profiler = new GCProfiler(); +console.log("dispose method:", typeof profiler[Symbol.dispose]); +console.log("start twice:", profiler.start(), profiler.start()); +console.log("first stop report:", typeof profiler.stop() === "object"); +console.log("second stop:", profiler.stop()); +console.log("dispose return:", profiler[Symbol.dispose]()); + +const active = new GCProfiler(); +active.start(); +console.log("active dispose:", active[Symbol.dispose]()); +console.log("stop after dispose:", active.stop()); diff --git a/test-parity/node-suite/v8/heap-snapshot/output.ts b/test-parity/node-suite/v8/heap-snapshot/output.ts index 5ebd6672d8..fe9188382a 100644 --- a/test-parity/node-suite/v8/heap-snapshot/output.ts +++ b/test-parity/node-suite/v8/heap-snapshot/output.ts @@ -14,52 +14,74 @@ console.log( ); const stream: any = v8.getHeapSnapshot({ exposeInternals: true }); -console.log( - "stream shape:", - typeof stream.read, - typeof stream.on, - typeof stream.pipe, - typeof stream.setEncoding, - typeof stream[Symbol.asyncIterator], -); -console.log("setEncoding returns this:", stream.setEncoding("utf8") === stream); -const [chunk] = await once(stream, "data"); -console.log( - "chunk:", - typeof chunk, - chunk.startsWith('{"snapshot"'), - chunk.includes('"node_count"'), -); -stream.destroy?.(); +try { + console.log( + "stream shape:", + typeof stream.read, + typeof stream.on, + typeof stream.pipe, + typeof stream.setEncoding, + typeof stream[Symbol.asyncIterator], + ); + console.log( + "setEncoding returns this:", + stream.setEncoding("utf8") === stream, + ); + const [chunk] = await once(stream, "data"); + console.log( + "chunk:", + typeof chunk, + chunk.startsWith('{"snapshot"'), + chunk.includes('"node_count"'), + ); +} finally { + stream.destroy?.(); +} const dir = fs.mkdtempSync(path.join(os.tmpdir(), "perry-v8-heap-")); -const file = path.join(dir, "explicit.heapsnapshot"); -const ret = v8.writeHeapSnapshot(file, {}); -const prefix = fs.readFileSync(ret, "utf8").slice(0, 40); -const stat = fs.statSync(ret); -console.log( - "write:", - ret === file, - path.basename(ret), - stat.size > 0, - prefix.startsWith('{"snapshot"'), - prefix.includes('"meta"'), -); -fs.rmSync(dir, { recursive: true, force: true }); +try { + const file = path.join(dir, "explicit.heapsnapshot"); + const ret = v8.writeHeapSnapshot(file, {}); + const prefix = fs.readFileSync(ret, "utf8").slice(0, 40); + const stat = fs.statSync(ret); + console.log( + "write:", + ret === file, + path.basename(ret), + stat.size > 0, + prefix.startsWith('{"snapshot"'), + prefix.includes('"meta"'), + ); +} finally { + fs.rmSync(dir, { recursive: true, force: true }); +} -for (const [label, call] of [ - ["get null options", () => v8.getHeapSnapshot(null as any)], - ["get number options", () => v8.getHeapSnapshot(123 as any)], - ["write bad path", () => v8.writeHeapSnapshot(123 as any)], - [ - "write null options", - () => v8.writeHeapSnapshot(path.join(os.tmpdir(), "perry-v8-bad.heapsnapshot"), null as any), - ], -] as const) { - try { - call(); - console.log(label + ": no throw"); - } catch (e: any) { - console.log(label + ":", e.name, e.code); +const validationDir = fs.mkdtempSync( + path.join(os.tmpdir(), "perry-v8-validation-"), +); +try { + for ( + const [label, call] of [ + ["get null options", () => v8.getHeapSnapshot(null as any)], + ["get number options", () => v8.getHeapSnapshot(123 as any)], + ["write bad path", () => v8.writeHeapSnapshot(123 as any)], + [ + "write null options", + () => + v8.writeHeapSnapshot( + path.join(validationDir, "invalid-options.heapsnapshot"), + null as any, + ), + ], + ] as const + ) { + try { + call(); + console.log(label + ": no throw"); + } catch (e: any) { + console.log(label + ":", e.name, e.code); + } } +} finally { + fs.rmSync(validationDir, { recursive: true, force: true }); } diff --git a/test-parity/node-suite/v8/heap-snapshot/semantic-schema.ts b/test-parity/node-suite/v8/heap-snapshot/semantic-schema.ts new file mode 100644 index 0000000000..dd3bb894f4 --- /dev/null +++ b/test-parity/node-suite/v8/heap-snapshot/semantic-schema.ts @@ -0,0 +1,37 @@ +import { getHeapSnapshot } from "node:v8"; + +const chunks: Buffer[] = []; +const stream = getHeapSnapshot(); +for await (const chunk of stream) chunks.push(Buffer.from(chunk)); +const snapshot: any = JSON.parse(Buffer.concat(chunks).toString("utf8")); +const meta = snapshot.snapshot.meta; + +console.log("top keys:", Object.keys(snapshot).sort().join(",")); +console.log("snapshot keys:", Object.keys(snapshot.snapshot).sort().join(",")); +console.log( + "meta arrays:", + [meta.node_fields, meta.node_types, meta.edge_fields, meta.edge_types].every( + Array.isArray, + ), +); +console.log( + "table arrays:", + [snapshot.nodes, snapshot.edges, snapshot.strings].every(Array.isArray), +); +console.log( + "node width:", + meta.node_fields.length > 0, + snapshot.nodes.length % meta.node_fields.length === 0, +); +console.log( + "edge width:", + meta.edge_fields.length > 0, + snapshot.edges.length % meta.edge_fields.length === 0, +); +console.log( + "counts:", + snapshot.snapshot.node_count > 0, + snapshot.snapshot.edge_count >= 0, + snapshot.strings.length > 0, +); +console.log("stream ended:", stream.readableEnded); diff --git a/test-parity/node-suite/v8/heap-snapshot/twice.ts b/test-parity/node-suite/v8/heap-snapshot/twice.ts new file mode 100644 index 0000000000..9f544648e2 --- /dev/null +++ b/test-parity/node-suite/v8/heap-snapshot/twice.ts @@ -0,0 +1,13 @@ +import { once } from "node:events"; +import { getHeapSnapshot } from "node:v8"; + +for (let i = 1; i <= 2; i++) { + const stream = getHeapSnapshot(); + try { + stream.resume(); + await once(stream, "end"); + console.log("snapshot " + i + ":", stream.readableEnded, stream.destroyed); + } finally { + stream.destroy(); + } +} diff --git a/test-parity/node-suite/v8/heap-statistics/code-shape.ts b/test-parity/node-suite/v8/heap-statistics/code-shape.ts new file mode 100644 index 0000000000..02835e7831 --- /dev/null +++ b/test-parity/node-suite/v8/heap-statistics/code-shape.ts @@ -0,0 +1,20 @@ +import { getHeapCodeStatistics } from "node:v8"; + +const stats: any = getHeapCodeStatistics(); +const expected = [ + "bytecode_and_metadata_size", + "code_and_metadata_size", + "cpu_profiler_metadata_size", + "external_script_source_size", +]; +const values = expected.map((key) => stats[key]); +console.log("keys:", Object.keys(stats).sort().join(",")); +console.log( + "exact:", + Object.keys(stats).sort().join(",") === expected.join(","), +); +console.log("numbers:", values.every((value) => typeof value === "number")); +console.log( + "finite nonnegative:", + values.every((value) => Number.isFinite(value) && value >= 0), +); diff --git a/test-parity/node-suite/v8/heap-statistics/cpp-shape.ts b/test-parity/node-suite/v8/heap-statistics/cpp-shape.ts new file mode 100644 index 0000000000..12357e623e --- /dev/null +++ b/test-parity/node-suite/v8/heap-statistics/cpp-shape.ts @@ -0,0 +1,42 @@ +import { getCppHeapStatistics } from "node:v8"; + +const expected = [ + "committed_size_bytes", + "detail_level", + "resident_size_bytes", + "space_statistics", + "type_names", + "used_size_bytes", +]; +for (const mode of [undefined, "brief", "detailed"] as const) { + const stats: any = getCppHeapStatistics(mode); + console.log("mode:", mode ?? "default", stats.detail_level); + console.log( + "keys:", + Object.keys(stats).sort().join(",") === expected.join(","), + ); + console.log( + "numbers:", + [ + stats.committed_size_bytes, + stats.resident_size_bytes, + stats.used_size_bytes, + ].every((value) => + typeof value === "number" && Number.isFinite(value) && value >= 0 + ), + ); + console.log( + "arrays:", + Array.isArray(stats.space_statistics), + Array.isArray(stats.type_names), + ); +} + +for (const value of ["invalid", 1, null] as const) { + try { + getCppHeapStatistics(value as any); + console.log("invalid:", String(value), "no throw"); + } catch (error: any) { + console.log("invalid:", String(value), error.name, error.code); + } +} diff --git a/test-parity/node-suite/v8/heap-statistics/general-shape.ts b/test-parity/node-suite/v8/heap-statistics/general-shape.ts new file mode 100644 index 0000000000..c5fe6e9969 --- /dev/null +++ b/test-parity/node-suite/v8/heap-statistics/general-shape.ts @@ -0,0 +1,36 @@ +import { getHeapStatistics } from "node:v8"; + +const stats: any = getHeapStatistics(); +const expected = [ + "does_zap_garbage", + "external_memory", + "heap_size_limit", + "malloced_memory", + "number_of_detached_contexts", + "number_of_native_contexts", + "peak_malloced_memory", + "total_allocated_bytes", + "total_available_size", + "total_global_handles_size", + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "used_global_handles_size", + "used_heap_size", +]; +const values = expected.map((key) => stats[key]); +console.log("keys:", Object.keys(stats).sort().join(",")); +console.log( + "exact:", + Object.keys(stats).sort().join(",") === expected.join(","), +); +console.log("types:", values.every((value) => typeof value === "number")); +console.log( + "finite nonnegative:", + values.every((value) => Number.isFinite(value) && value >= 0), +); +console.log( + "heap relation:", + stats.used_heap_size <= stats.total_heap_size, + stats.used_global_handles_size <= stats.total_global_handles_size, +); diff --git a/test-parity/node-suite/v8/heap-statistics/spaces-shape.ts b/test-parity/node-suite/v8/heap-statistics/spaces-shape.ts new file mode 100644 index 0000000000..edc20598fc --- /dev/null +++ b/test-parity/node-suite/v8/heap-statistics/spaces-shape.ts @@ -0,0 +1,43 @@ +import { getHeapSpaceStatistics } from "node:v8"; + +const spaces: any[] = getHeapSpaceStatistics(); +const keys = [ + "physical_space_size", + "space_available_size", + "space_name", + "space_size", + "space_used_size", +]; +console.log("array:", Array.isArray(spaces), spaces.length > 0); +console.log( + "names unique:", + new Set(spaces.map((space) => space.space_name)).size === spaces.length, +); +console.log( + "entry keys:", + spaces.every((space) => + Object.keys(space).sort().join(",") === keys.join(",") + ), +); +console.log( + "name types:", + spaces.every((space) => + typeof space.space_name === "string" && space.space_name.length > 0 + ), +); +console.log( + "number types:", + spaces.every((space) => + keys.slice(0, 2).concat(keys.slice(3)).every((key) => + typeof space[key] === "number" + ) + ), +); +console.log( + "finite nonnegative:", + spaces.every((space) => + keys.slice(0, 2).concat(keys.slice(3)).every((key) => + Number.isFinite(space[key]) && space[key] >= 0 + ) + ), +); diff --git a/test-parity/node-suite/v8/promise-hooks/completion-order.ts b/test-parity/node-suite/v8/promise-hooks/completion-order.ts new file mode 100644 index 0000000000..144176901e --- /dev/null +++ b/test-parity/node-suite/v8/promise-hooks/completion-order.ts @@ -0,0 +1,21 @@ +import { promiseHooks } from "node:v8"; + +const events: string[] = []; +let child: Promise; +const stops = [ + promiseHooks.onBefore((promise) => { + if (promise === child) events.push("before"); + }), + promiseHooks.onAfter((promise) => { + if (promise === child) events.push("after"); + }), + promiseHooks.onSettled((promise) => { + if (promise === child) events.push("settled"); + }), +]; + +child = Promise.resolve().then(() => events.push("callback")); +child.then(() => { + for (const stop of stops) stop(); + console.log("events:", events.join(",")); +}); diff --git a/test-parity/node-suite/v8/promise-hooks/init-parent.ts b/test-parity/node-suite/v8/promise-hooks/init-parent.ts new file mode 100644 index 0000000000..baca27c226 --- /dev/null +++ b/test-parity/node-suite/v8/promise-hooks/init-parent.ts @@ -0,0 +1,18 @@ +import { promiseHooks } from "node:v8"; + +let seenPromise: Promise | undefined; +let seenParent: Promise | undefined; +const stop = promiseHooks.onInit((promise, parent) => { + seenPromise = promise; + seenParent = parent; +}); + +const parent = Promise.resolve(1); +console.log("root:", seenPromise === parent, seenParent === undefined); +const child = parent.then((value) => value + 1); +console.log("child:", seenPromise === child, seenParent === parent); +stop(); +seenPromise = undefined; +Promise.resolve(2); +console.log("stopped:", seenPromise === undefined); +await child; diff --git a/test-parity/node-suite/v8/promise-hooks/lifecycle-order.ts b/test-parity/node-suite/v8/promise-hooks/lifecycle-order.ts new file mode 100644 index 0000000000..26dd8d82c0 --- /dev/null +++ b/test-parity/node-suite/v8/promise-hooks/lifecycle-order.ts @@ -0,0 +1,34 @@ +import { promiseHooks } from "node:v8"; + +const events: string[] = []; +let root: Promise; +let child: Promise; +const stop = promiseHooks.createHook({ + init(promise, parent) { + if (parent === undefined) { + root = promise; + events.push("init:root"); + } else if (parent === root) { + events.push("init:child:parent"); + } + }, + before(promise) { + if (promise === child) events.push("before:child"); + }, + after(promise) { + if (promise === child) events.push("after:child"); + }, + settled(promise) { + if (promise === root) events.push("settled:root"); + if (promise === child) events.push("settled:child"); + }, +}); + +root = Promise.resolve("value"); +child = root.then(() => { + events.push("callback"); +}); +child.then(() => { + stop(); + console.log("events:", events.join(",")); +}); diff --git a/test-parity/node-suite/v8/promise-hooks/surface.ts b/test-parity/node-suite/v8/promise-hooks/surface.ts new file mode 100644 index 0000000000..cb42f26069 --- /dev/null +++ b/test-parity/node-suite/v8/promise-hooks/surface.ts @@ -0,0 +1,27 @@ +import { promiseHooks } from "node:v8"; + +console.log("type:", typeof promiseHooks); +console.log("keys:", Object.keys(promiseHooks).join(",")); +for ( + const key of [ + "createHook", + "onInit", + "onBefore", + "onAfter", + "onSettled", + ] as const +) { + const descriptor = Object.getOwnPropertyDescriptor(promiseHooks, key)!; + console.log( + key, + typeof promiseHooks[key], + promiseHooks[key].length, + descriptor.enumerable, + descriptor.writable, + descriptor.configurable, + ); +} + +const stop = promiseHooks.createHook({}); +console.log("stop:", typeof stop, stop.length, stop.name); +console.log("stop returns:", stop(), stop()); diff --git a/test-parity/node-suite/v8/promise-hooks/validation.ts b/test-parity/node-suite/v8/promise-hooks/validation.ts new file mode 100644 index 0000000000..2262cb976a --- /dev/null +++ b/test-parity/node-suite/v8/promise-hooks/validation.ts @@ -0,0 +1,22 @@ +import { promiseHooks } from "node:v8"; + +for ( + const [label, call] of [ + ["onInit undefined", () => promiseHooks.onInit(undefined as any)], + ["onBefore number", () => promiseHooks.onBefore(1 as any)], + ["onAfter async", () => promiseHooks.onAfter(async () => {})], + [ + "onSettled generator", + () => promiseHooks.onSettled(async function* () {}), + ], + ["createHook null", () => promiseHooks.createHook(null as any)], + ["createHook bad init", () => promiseHooks.createHook({ init: 1 as any })], + ] as const +) { + try { + call(); + console.log(label + ": no throw"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serialize/buffers-views.ts b/test-parity/node-suite/v8/serialize/buffers-views.ts new file mode 100644 index 0000000000..b5b8960860 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/buffers-views.ts @@ -0,0 +1,33 @@ +import { Buffer } from "node:buffer"; +import { deserialize, serialize } from "node:v8"; + +const arrayBuffer = new Uint8Array([10, 20, 30, 40]).buffer; +const dataView = new DataView(arrayBuffer, 1, 2); +const buffer = Buffer.from([5, 6, 7]); + +for (const value of [arrayBuffer, dataView, buffer] as const) { + const result: any = deserialize(serialize(value)); + const bytes = result instanceof ArrayBuffer + ? [...new Uint8Array(result)] + : [...new Uint8Array(result.buffer, result.byteOffset, result.byteLength)]; + console.log( + value.constructor.name + ":", + result.constructor.name, + result.byteLength, + bytes.join(","), + ); +} + +const payload = serialize({ ok: 1 }); +for ( + const input of [ + payload, + new Uint8Array(payload), + new DataView(payload.buffer, payload.byteOffset, payload.byteLength), + ] as const +) { + console.log( + "input " + input.constructor.name + ":", + JSON.stringify(deserialize(input)), + ); +} diff --git a/test-parity/node-suite/v8/serialize/builtins.ts b/test-parity/node-suite/v8/serialize/builtins.ts new file mode 100644 index 0000000000..f8b7357670 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/builtins.ts @@ -0,0 +1,15 @@ +import { deserialize, serialize } from "node:v8"; + +const source: any = { + date: new Date("2020-01-02T03:04:05.000Z"), + regexp: /a+b/giu, +}; +const result: any = deserialize(serialize(source)); + +console.log("date:", result.date instanceof Date, result.date.toISOString()); +console.log( + "regexp:", + result.regexp instanceof RegExp, + result.regexp.source, + result.regexp.flags, +); diff --git a/test-parity/node-suite/v8/serialize/corrupt-input.ts b/test-parity/node-suite/v8/serialize/corrupt-input.ts new file mode 100644 index 0000000000..1048be6f46 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/corrupt-input.ts @@ -0,0 +1,17 @@ +import { deserialize } from "node:v8"; + +for ( + const [label, value] of [ + ["empty", Buffer.alloc(0)], + ["header only", Buffer.from([0xff, 0x0f])], + ["random", Buffer.from([1, 2, 3, 4])], + ["truncated object", Buffer.from("ff0f6f220161", "hex")], + ] as const +) { + try { + deserialize(value); + console.log(label + ": no throw"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serialize/error-like.ts b/test-parity/node-suite/v8/serialize/error-like.ts new file mode 100644 index 0000000000..e99df92acf --- /dev/null +++ b/test-parity/node-suite/v8/serialize/error-like.ts @@ -0,0 +1,29 @@ +import { deserialize, serialize } from "node:v8"; + +const source = Object.assign( + new TypeError("boom", { cause: new RangeError("root") }), + { + code: "E_SAMPLE", + }, +); +const result: any = deserialize(serialize(source)); + +console.log( + "error:", + result instanceof Error, + result instanceof TypeError, + result.name, + result.message, +); +console.log( + "cause:", + result.cause instanceof RangeError, + result.cause.name, + result.cause.message, +); +console.log("stack type:", typeof result.stack); +console.log( + "extra omitted:", + Object.prototype.hasOwnProperty.call(result, "code"), + result.code, +); diff --git a/test-parity/node-suite/v8/serialize/float16-array.ts b/test-parity/node-suite/v8/serialize/float16-array.ts new file mode 100644 index 0000000000..ca99f32aa4 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/float16-array.ts @@ -0,0 +1,8 @@ +import { deserialize, serialize } from "node:v8"; + +const source = new Float16Array([1.5, -2, 0]); +const result: any = deserialize(serialize(source)); +console.log("brand:", result instanceof Float16Array, result.constructor.name); +console.log("shape:", result.length, result.byteLength); +console.log("values:", [...result].join(",")); +console.log("fresh:", result !== source, result.buffer !== source.buffer); diff --git a/test-parity/node-suite/v8/serialize/invalid-input.ts b/test-parity/node-suite/v8/serialize/invalid-input.ts new file mode 100644 index 0000000000..5c66c74f5a --- /dev/null +++ b/test-parity/node-suite/v8/serialize/invalid-input.ts @@ -0,0 +1,18 @@ +import { deserialize } from "node:v8"; + +for ( + const [label, value] of [ + ["undefined", undefined], + ["null", null], + ["string", "bad"], + ["object", {}], + ["arraybuffer", new ArrayBuffer(4)], + ] as const +) { + try { + deserialize(value as any); + console.log(label + ": no throw"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serialize/invalid-values.ts b/test-parity/node-suite/v8/serialize/invalid-values.ts new file mode 100644 index 0000000000..741bd47574 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/invalid-values.ts @@ -0,0 +1,17 @@ +import { serialize } from "node:v8"; + +for ( + const [label, value] of [ + ["function", () => 1], + ["symbol", Symbol("x")], + ["weakmap", new WeakMap()], + ["promise", Promise.resolve(1)], + ] as const +) { + try { + serialize(value); + console.log(label + ": no throw"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serialize/map-set.ts b/test-parity/node-suite/v8/serialize/map-set.ts new file mode 100644 index 0000000000..6cf418800e --- /dev/null +++ b/test-parity/node-suite/v8/serialize/map-set.ts @@ -0,0 +1,24 @@ +import { deserialize, serialize } from "node:v8"; + +const key = { key: true }; +const source = { + map: new Map([["a", 1], [key, key]]), + set: new Set([3, "two", key]), +}; +const result: any = deserialize(serialize(source)); +const clonedKey = [...result.map.keys()][1]; + +console.log( + "map:", + result.map instanceof Map, + result.map.size, + JSON.stringify([...result.map]), +); +console.log( + "set:", + result.set instanceof Set, + result.set.size, + JSON.stringify([...result.set]), +); +console.log("map identity:", result.map.get(clonedKey) === clonedKey); +console.log("shared identity:", result.set.has(clonedKey)); diff --git a/test-parity/node-suite/v8/serialize/objects-collections.ts b/test-parity/node-suite/v8/serialize/objects-collections.ts new file mode 100644 index 0000000000..a4b907cdc6 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/objects-collections.ts @@ -0,0 +1,21 @@ +import { deserialize, serialize } from "node:v8"; + +const source = { + object: { a: 1, nested: { b: "two" } }, + array: [1, , 3], +}; +const result: any = deserialize(serialize(source)); + +console.log("object:", JSON.stringify(result.object)); +console.log( + "array:", + result.array.length, + 1 in result.array, + JSON.stringify(result.array), +); +console.log( + "fresh:", + result !== source, + result.object !== source.object, + result.array !== source.array, +); diff --git a/test-parity/node-suite/v8/serialize/primitives.ts b/test-parity/node-suite/v8/serialize/primitives.ts new file mode 100644 index 0000000000..3ad30ac2f9 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/primitives.ts @@ -0,0 +1,28 @@ +import { deserialize, serialize } from "node:v8"; + +for ( + const [label, value] of [ + ["undefined", undefined], + ["null", null], + ["true", true], + ["false", false], + ["zero", 0], + ["negative-zero", -0], + ["integer", 42], + ["fraction", -1.25], + ["nan", NaN], + ["infinity", Infinity], + ["string", "héllo"], + ["bigint", 9007199254740993n], + ] as const +) { + const result = deserialize(serialize(value)); + const normalized = typeof result === "bigint" + ? result + "n" + : Number.isNaN(result) + ? "NaN" + : Object.is(result, -0) + ? "-0" + : String(result); + console.log(label + ":", typeof result, normalized); +} diff --git a/test-parity/node-suite/v8/serialize/references-cycles.ts b/test-parity/node-suite/v8/serialize/references-cycles.ts new file mode 100644 index 0000000000..6f1eb23921 --- /dev/null +++ b/test-parity/node-suite/v8/serialize/references-cycles.ts @@ -0,0 +1,16 @@ +import { deserialize, serialize } from "node:v8"; + +const shared = { marker: "shared" }; +const root: any = { left: shared, right: shared, array: [shared] }; +root.self = root; +root.array.push(root.array); + +const result: any = deserialize(serialize(root)); +console.log("root cycle:", result.self === result); +console.log( + "shared object:", + result.left === result.right, + result.left === result.array[0], +); +console.log("array cycle:", result.array[1] === result.array); +console.log("fresh graph:", result !== root, result.left !== shared); diff --git a/test-parity/node-suite/v8/serialize/typed-arrays.ts b/test-parity/node-suite/v8/serialize/typed-arrays.ts new file mode 100644 index 0000000000..8b506a5d5c --- /dev/null +++ b/test-parity/node-suite/v8/serialize/typed-arrays.ts @@ -0,0 +1,32 @@ +import { deserialize, serialize } from "node:v8"; + +for ( + const value of [ + new Int8Array([-1, 2]), + new Uint8Array([0, 255]), + new Uint8ClampedArray([-1, 300]), + new Int16Array([-2, 300]), + new Uint16Array([2, 65535]), + new Int32Array([-3, 70000]), + new Uint32Array([3, 4000000000]), + new Float32Array([1.5, -2.25]), + new Float64Array([Math.PI, -0]), + new BigInt64Array([-1n, 2n]), + new BigUint64Array([1n, 2n]), + ] as const +) { + const result: any = deserialize(serialize(value)); + const values = [...result].map((item: any) => + typeof item === "bigint" + ? item + "n" + : Object.is(item, -0) + ? "-0" + : String(item) + ); + console.log( + value.constructor.name + ":", + result.constructor.name, + result.byteLength, + values.join(","), + ); +} diff --git a/test-parity/node-suite/v8/serializer/header-values.ts b/test-parity/node-suite/v8/serializer/header-values.ts new file mode 100644 index 0000000000..5149f0de5b --- /dev/null +++ b/test-parity/node-suite/v8/serializer/header-values.ts @@ -0,0 +1,20 @@ +import { DefaultDeserializer, DefaultSerializer } from "node:v8"; + +const serializer = new DefaultSerializer(); +console.log("writeHeader:", serializer.writeHeader()); +for ( + const value of [undefined, null, true, 42, "text", { + nested: [1, 2], + }] as const +) { + console.log("writeValue:", serializer.writeValue(value)); +} +const buffer = serializer.releaseBuffer(); +console.log("buffer:", Buffer.isBuffer(buffer), buffer.length > 0); + +const deserializer = new DefaultDeserializer(buffer); +console.log("readHeader:", deserializer.readHeader()); +for (let i = 0; i < 6; i++) { + const value: any = deserializer.readValue(); + console.log("readValue:", typeof value, JSON.stringify(value)); +} diff --git a/test-parity/node-suite/v8/serializer/input-view-family.ts b/test-parity/node-suite/v8/serializer/input-view-family.ts new file mode 100644 index 0000000000..7023b15bdb --- /dev/null +++ b/test-parity/node-suite/v8/serializer/input-view-family.ts @@ -0,0 +1,35 @@ +import { deserialize, serialize } from "node:v8"; + +const payload = serialize("xxxx"); +console.log( + "aligned payload:", + payload.byteLength, + payload.byteLength % 8 === 0, +); + +const constructors = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float16Array, + Float32Array, + Float64Array, + BigInt64Array, + BigUint64Array, + DataView, +]; +for (const Constructor of constructors) { + const bytesPerElement = (Constructor as any).BYTES_PER_ELEMENT ?? 1; + const view = Constructor === DataView + ? new DataView(payload.buffer, payload.byteOffset, payload.byteLength) + : new (Constructor as any)( + payload.buffer, + payload.byteOffset, + payload.byteLength / bytesPerElement, + ); + console.log(Constructor.name + ":", deserialize(view) === "xxxx"); +} diff --git a/test-parity/node-suite/v8/serializer/raw-primitives.ts b/test-parity/node-suite/v8/serializer/raw-primitives.ts new file mode 100644 index 0000000000..d2b6f7a388 --- /dev/null +++ b/test-parity/node-suite/v8/serializer/raw-primitives.ts @@ -0,0 +1,19 @@ +import { Deserializer, Serializer } from "node:v8"; + +const serializer = new Serializer(); +serializer.writeUint32(0); +serializer.writeUint32(0xffffffff); +serializer.writeUint64(0x12345678, 0x90abcdef); +serializer.writeDouble(-0.25); +serializer.writeRawBytes(new Uint8Array([1, 2, 255])); +const buffer = serializer.releaseBuffer(); +console.log("buffer:", Buffer.isBuffer(buffer), buffer.length); + +const deserializer = new Deserializer(buffer); +console.log("uint32:", deserializer.readUint32(), deserializer.readUint32()); +console.log( + "uint64:", + deserializer.readUint64().map((value) => value.toString(16)).join(","), +); +console.log("double:", deserializer.readDouble()); +console.log("raw:", [...deserializer.readRawBytes(3)].join(",")); diff --git a/test-parity/node-suite/v8/serializer/raw-views.ts b/test-parity/node-suite/v8/serializer/raw-views.ts new file mode 100644 index 0000000000..1455fd6498 --- /dev/null +++ b/test-parity/node-suite/v8/serializer/raw-views.ts @@ -0,0 +1,18 @@ +import { Deserializer, Serializer } from "node:v8"; + +const bytes = Buffer.from([99, 10, 20, 30, 88]); +const views = [ + bytes.subarray(1, 4), + new Uint8Array(bytes.buffer, bytes.byteOffset + 1, 3), + new DataView(bytes.buffer, bytes.byteOffset + 1, 3), +]; + +for (const view of views) { + const serializer = new Serializer(); + serializer.writeRawBytes(view); + const deserializer = new Deserializer(serializer.releaseBuffer()); + console.log( + view.constructor.name + ":", + [...deserializer.readRawBytes(3)].join(","), + ); +} diff --git a/test-parity/node-suite/v8/serializer/release-reuse.ts b/test-parity/node-suite/v8/serializer/release-reuse.ts new file mode 100644 index 0000000000..b676b66140 --- /dev/null +++ b/test-parity/node-suite/v8/serializer/release-reuse.ts @@ -0,0 +1,23 @@ +import { Buffer } from "node:buffer"; +import { Serializer } from "node:v8"; + +const serializer = new Serializer(); +serializer.writeUint32(7); +const first = serializer.releaseBuffer(); +console.log( + "first:", + Buffer.isBuffer(first), + first.length, + first.toString("hex"), +); + +for (const label of ["release again", "write after release"] as const) { + try { + const result = label === "release again" + ? serializer.releaseBuffer() + : serializer.writeUint32(8); + console.log(label + ":", result === undefined ? "undefined" : "value"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serializer/transfer-array-buffer.ts b/test-parity/node-suite/v8/serializer/transfer-array-buffer.ts new file mode 100644 index 0000000000..bb50ff4ecf --- /dev/null +++ b/test-parity/node-suite/v8/serializer/transfer-array-buffer.ts @@ -0,0 +1,20 @@ +import { Deserializer, Serializer } from "node:v8"; + +const source = new Uint8Array([1, 2, 3]).buffer; +const serializer = new Serializer(); +serializer.writeHeader(); +serializer.transferArrayBuffer(7, source); +serializer.writeValue({ buffer: source }); +console.log( + "source intact:", + source.byteLength, + [...new Uint8Array(source)].join(","), +); + +const target = new Uint8Array([9, 8, 7]).buffer; +const deserializer = new Deserializer(serializer.releaseBuffer()); +deserializer.readHeader(); +deserializer.transferArrayBuffer(7, target); +const result: any = deserializer.readValue(); +console.log("target identity:", result.buffer === target); +console.log("target bytes:", [...new Uint8Array(result.buffer)].join(",")); diff --git a/test-parity/node-suite/v8/serializer/validation.ts b/test-parity/node-suite/v8/serializer/validation.ts new file mode 100644 index 0000000000..7e274b758a --- /dev/null +++ b/test-parity/node-suite/v8/serializer/validation.ts @@ -0,0 +1,27 @@ +import { Buffer } from "node:buffer"; +import { Deserializer, Serializer } from "node:v8"; + +for ( + const [label, call] of [ + [ + "writeRawBytes string", + () => new Serializer().writeRawBytes("bad" as any), + ], + ["writeRawBytes object", () => new Serializer().writeRawBytes({} as any)], + [ + "readDouble empty", + () => new Deserializer(new Serializer().releaseBuffer()).readDouble(), + ], + [ + "readRawBytes negative", + () => new Deserializer(Buffer.alloc(4)).readRawBytes(-1), + ], + ] as const +) { + try { + call(); + console.log(label + ": no throw"); + } catch (error: any) { + console.log(label + ":", error.name, error.code ?? "no-code"); + } +} diff --git a/test-parity/node-suite/v8/serializer/wire-version.ts b/test-parity/node-suite/v8/serializer/wire-version.ts new file mode 100644 index 0000000000..3d9a482637 --- /dev/null +++ b/test-parity/node-suite/v8/serializer/wire-version.ts @@ -0,0 +1,17 @@ +import { DefaultDeserializer, DefaultSerializer } from "node:v8"; + +const serializer = new DefaultSerializer(); +serializer.writeHeader(); +serializer.writeValue("wire"); +const deserializer = new DefaultDeserializer(serializer.releaseBuffer()); + +console.log("before header:", deserializer.getWireFormatVersion()); +console.log("header:", deserializer.readHeader()); +const version = deserializer.getWireFormatVersion(); +console.log( + "version:", + typeof version, + Number.isInteger(version), + version === 15, +); +console.log("value:", deserializer.readValue()); diff --git a/test-parity/node-suite/v8/startup-snapshot/shape.ts b/test-parity/node-suite/v8/startup-snapshot/shape.ts index 1bb2b7c4b5..fcc4611e91 100644 --- a/test-parity/node-suite/v8/startup-snapshot/shape.ts +++ b/test-parity/node-suite/v8/startup-snapshot/shape.ts @@ -9,11 +9,13 @@ console.log("addDeserializeCallback:", typeof s.addDeserializeCallback); console.log("setDeserializeMainFunction:", typeof s.setDeserializeMainFunction); console.log("isBuildingSnapshot():", s.isBuildingSnapshot()); -for (const [name, fn] of [ - ["addSerializeCallback", s.addSerializeCallback], - ["addDeserializeCallback", s.addDeserializeCallback], - ["setDeserializeMainFunction", s.setDeserializeMainFunction], -] as const) { +for ( + const [name, fn] of [ + ["addSerializeCallback", s.addSerializeCallback], + ["addDeserializeCallback", s.addDeserializeCallback], + ["setDeserializeMainFunction", s.setDeserializeMainFunction], + ] as const +) { try { fn(() => {}); console.log(name + ": no throw"); diff --git a/test-parity/node-suite/v8/strings/one-byte.ts b/test-parity/node-suite/v8/strings/one-byte.ts new file mode 100644 index 0000000000..8c56fad03f --- /dev/null +++ b/test-parity/node-suite/v8/strings/one-byte.ts @@ -0,0 +1,17 @@ +import { isStringOneByteRepresentation } from "node:v8"; + +for (const value of ["", "abc", "é", "€", "😀"] as const) { + console.log( + JSON.stringify(value) + ":", + isStringOneByteRepresentation(value), + ); +} + +for (const value of [undefined, 1, null] as const) { + try { + isStringOneByteRepresentation(value as any); + console.log("invalid " + String(value) + ": no throw"); + } catch (error: any) { + console.log("invalid " + String(value) + ":", error.name, error.code); + } +} diff --git a/test-parity/node-suite/v8/version-tag/flags-isolated.ts b/test-parity/node-suite/v8/version-tag/flags-isolated.ts new file mode 100644 index 0000000000..86b5d0b2ac --- /dev/null +++ b/test-parity/node-suite/v8/version-tag/flags-isolated.ts @@ -0,0 +1,16 @@ +import { cachedDataVersionTag, setFlagsFromString } from "node:v8"; + +const before = cachedDataVersionTag(); +console.log("return:", setFlagsFromString("--allow_natives_syntax")); +const after = cachedDataVersionTag(); +console.log("changed:", before !== after); +console.log("stable after:", after === cachedDataVersionTag()); + +for (const value of [undefined, 1] as const) { + try { + setFlagsFromString(value as any); + console.log("invalid " + String(value) + ": no throw"); + } catch (error: any) { + console.log("invalid " + String(value) + ":", error.name, error.code); + } +} diff --git a/test-parity/node-suite/v8/version-tag/stability.ts b/test-parity/node-suite/v8/version-tag/stability.ts new file mode 100644 index 0000000000..7da0d2f6aa --- /dev/null +++ b/test-parity/node-suite/v8/version-tag/stability.ts @@ -0,0 +1,12 @@ +import { cachedDataVersionTag } from "node:v8"; + +const first = cachedDataVersionTag(); +const second = cachedDataVersionTag(); +console.log("type:", typeof first); +console.log( + "uint32:", + Number.isInteger(first), + first >= 0, + first <= 0xffffffff, +); +console.log("stable:", first === second); diff --git a/test-parity/node_suite_baseline.json b/test-parity/node_suite_baseline.json index b2287fc0bc..1f15147606 100644 --- a/test-parity/node_suite_baseline.json +++ b/test-parity/node_suite_baseline.json @@ -198,8 +198,8 @@ "total": 86 }, "v8": { - "pass": 4, - "total": 4 + "pass": 14, + "total": 46 }, "vm": { "pass": 21,