From f67678b4227bfe415d684f406c797b397ff6f776 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 29 Oct 2026 23:16:13 -0500 Subject: [PATCH] Pipe decoded socket strings and give lane jobs their own tmpdirs After socket.setEncoding(enc), socket.pipe(destination) wrote the raw bytes it read to the destination. It now writes the decoded strings, as Node's pipe writes each string chunk: the destination receives their UTF-8 bytes, and a partial character the decoder holds at EOF is written before the destination ends. tests/node-parallel/run.mjs runs each job's files with its own TEST_THREAD_ID, as Node's test runner does, so test/common/tmpdir gives concurrent files different directories instead of one shared .tmp.0. --- .../generated/node-v24-internal.json | 111 ++++++++++++------ internal/compatibility/static-support.json | 2 +- .../corpus/net-socket-encoding-pipe.cjs.json | 6 + packages/runtime/src/scr_net.c | 54 ++++++--- tests/corpus/net-socket-encoding-pipe.cjs | 44 +++++++ .../net-socket-encoding-pipe.cjs.json | 1 + tests/harness/node-parallel-runner.test.ts | 47 ++++++++ tests/node-parallel/README.md | 2 +- tests/node-parallel/run.mjs | 13 +- 9 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-socket-encoding-pipe.cjs.json create mode 100644 tests/corpus/net-socket-encoding-pipe.cjs create mode 100644 tests/harness/corpus-costs/net-socket-encoding-pipe.cjs.json diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 961b40bd4..3da043d37 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -54790,6 +54790,7 @@ "tests/corpus/2988-http-static-client-abort.ts", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", @@ -56346,7 +56347,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -56434,7 +56436,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -59466,6 +59469,7 @@ "tests/corpus/2689-http-server-timeout-properties.ts", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs" @@ -61731,7 +61735,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -62044,7 +62049,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -63875,6 +63881,7 @@ "tests/corpus/2689-http-server-timeout-properties.ts", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs" @@ -63978,7 +63985,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -64079,7 +64087,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72076,6 +72085,7 @@ "tests/corpus/5659-net-blocklist-socketaddress.ts", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", @@ -72692,7 +72702,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72719,7 +72730,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72782,7 +72794,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72809,7 +72822,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72836,7 +72850,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72863,7 +72878,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -72890,7 +72906,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73238,7 +73255,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73283,7 +73301,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73310,7 +73329,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73376,7 +73396,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73403,7 +73424,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73502,7 +73524,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -73586,7 +73609,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74235,7 +74259,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74262,7 +74287,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74289,7 +74315,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74316,7 +74343,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74343,7 +74371,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74370,7 +74399,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74397,7 +74427,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74424,7 +74455,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74451,7 +74483,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74514,7 +74547,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -74541,7 +74575,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -76000,6 +76035,7 @@ "tests/corpus/6397-event-loop-metrics.ts", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", @@ -76311,7 +76347,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -96254,6 +96291,7 @@ "tests/corpus/7442-tls-server-contexts.mjs", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", @@ -96666,7 +96704,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -96698,7 +96737,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" @@ -96753,7 +96793,8 @@ "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", - "tests/corpus/net-server-socket-peer-close.cjs" + "tests/corpus/net-server-socket-peer-close.cjs", + "tests/corpus/net-socket-encoding-pipe.cjs" ] }, "anchorSource": "exact" diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 2d74d4de5..50d41994e 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -479,7 +479,7 @@ { "symbols": ["setImmediate", "setInterval", "setTimeout", "clearImmediate", "clearInterval", "clearTimeout", "timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "immediate.hasRef", "immediate.ref", "immediate.unref", "timeout.hasRef", "timeout.ref", "timeout.unref", "timeout.refresh"], "status": "partial", "evidence": ["tests/corpus/1804-timers-module.ts", "tests/corpus/1805-timer-callback-args.ts", "tests/corpus/2093-timers-promises.ts", "tests/corpus/2852-timers-promises-interval.ts", "tests/corpus/5830-timers-promises-signal.ts"] }, { "symbols": ["timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/corpus/5839-timers-promises-scheduler.ts", "tests/corpus/5889-timers-scheduler-signal.ts"] }, { "symbols": ["stream.finished", "stream.pipeline", "stream.getDefaultHighWaterMark", "readable.push", "readable.unshift", "readable.read", "readable.pause", "readable.resume", "readable.isPaused", "readable.setEncoding", "readable.pipe", "readable.unpipe", "readable.destroy", "writable.write", "writable.end", "writable.cork", "writable.uncork", "writable.setDefaultEncoding"], "status": "partial", "evidence": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1688-stream-writable-basics.ts", "tests/corpus/1814-stream-pipeline.cjs", "tests/corpus/5302-stream-promises-options.ts", "tests/corpus/5303-stream-finished-callback-opts.cjs", "tests/corpus/5425-pipeline-generator-stage.ts", "tests/corpus/5428-pipeline-generator-stages.cjs", "tests/corpus/5617-pipeline-function-ends.ts", "tests/corpus/5619-pipeline-function-ends.cjs", "tests/corpus/writable-error-callbacks.ts"] }, - { "symbols": ["net.createServer", "net.connect", "net.createConnection", "net.getDefaultAutoSelectFamilyAttemptTimeout", "net.setDefaultAutoSelectFamilyAttemptTimeout", "server.address", "server.close", "server.listen", "socket.destroy", "socket.end", "socket.pause", "socket.resume", "socket.setEncoding", "socket.setTimeout", "socket.write"], "status": "partial", "evidence": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs"] }, + { "symbols": ["net.createServer", "net.connect", "net.createConnection", "net.getDefaultAutoSelectFamilyAttemptTimeout", "net.setDefaultAutoSelectFamilyAttemptTimeout", "server.address", "server.close", "server.listen", "socket.destroy", "socket.end", "socket.pause", "socket.resume", "socket.setEncoding", "socket.setTimeout", "socket.write"], "status": "partial", "evidence": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs", "tests/corpus/net-socket-set-encoding-names.ts", "tests/corpus/net-socket-write-rejected.cjs", "tests/corpus/net-socket-method-values.cjs", "tests/corpus/net-paused-socket-exit.cjs", "tests/corpus/net-server-socket-peer-close.cjs", "tests/corpus/net-socket-encoding-pipe.cjs"] }, { "symbols": ["dgram.createSocket", "socket.address", "socket.bind", "socket.close", "socket.connect", "socket.ref", "socket.remoteAddress", "socket.send", "socket.unref"], "status": "partial", "evidence": ["tests/corpus/2597-dgram-send-ladders.cjs"] }, { "symbols": ["net.SocketAddress", "net.BlockList"], "status": "partial", "evidence": ["tests/corpus/5659-net-blocklist-socketaddress.ts", "tests/fixtures/node-types/net-blocklist.ts"] }, { "symbols": ["SocketAddress.parse", "BlockList.isBlockList", "blockList.addAddress", "blockList.addAddresses", "blockList.addCIDR", "blockList.addCIDRs", "blockList.addRange", "blockList.addSubnet", "blockList.check", "blockList.clear", "blockList.fromJSON", "blockList.removeAddress", "blockList.removeCIDR", "blockList.removeRange", "blockList.removeSubnet", "blockList.toJSON"], "status": "supported", "evidence": ["tests/corpus/5659-net-blocklist-socketaddress.ts", "tests/fixtures/node-types/net-blocklist.ts"] }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-socket-encoding-pipe.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-socket-encoding-pipe.cjs.json new file mode 100644 index 000000000..59d8f4e80 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/net-socket-encoding-pipe.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/net-socket-encoding-pipe.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 2c9793d0b..92fd7c9f1 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -1562,6 +1562,19 @@ static void scr_net_sock_fire_data(ScrNetSocket *s, ScrBytes *chunk, ScrStr *tex scr_bytes_release(empty); } +/* One decoded 'data' string after setEncoding: the pipe destination + * writes it, as Node's pipe writes the string chunk (UTF-8), and the + * 'data' listeners receive it. */ +static void scr_net_sock_emit_text(ScrNetSocket *s, ScrStr *text) { + if (s->pipe_dst) { + char *owned; + const char *bytes = scr_str_utf8(text, &owned); + scr_net_sock_write_raw(s->pipe_dst, bytes, text->len); + scr_str_utf8_end(owned); + } + if (s->data_ls.n > 0) scr_net_sock_fire_data(s, NULL, text); +} + /* EOF: 'end' fires now (directly — Node's poll-phase emit), then the * write half ends (allowHalfOpen false), and the fd closes once the FIN * is out. 'close' waits for the sweep. */ @@ -1573,6 +1586,15 @@ static void scr_net_sock_eof(ScrNetSocket *s) { s->native_eof(s->native_ctx); if (scr_exc_pending()) return; } + if (s->encoding != NULL && (s->data_ls.n > 0 || s->pipe_dst != NULL)) { + /* the decoder's end(): a held partial sequence is the last 'data', + * and the pipe destination receives it before its end() */ + ScrStr *tail = scr_strdec_end(s->encoding, s->dec_pending); + s->dec_pending = 0; + if (tail->len > 0) scr_net_sock_emit_text(s, tail); + scr_str_release(tail); + if (scr_exc_pending()) return; + } if (s->pipe_dst) { /* pipe's end-the-destination default */ ScrNetSocket *dst = s->pipe_dst; @@ -1581,14 +1603,6 @@ static void scr_net_sock_eof(ScrNetSocket *s) { scr_net_sock_maybe_finish_write(dst); } } - if (s->encoding != NULL && s->data_ls.n > 0) { - /* the decoder's end(): a held partial sequence is the last 'data' */ - ScrStr *tail = scr_strdec_end(s->encoding, s->dec_pending); - s->dec_pending = 0; - if (tail->len > 0) scr_net_sock_fire_data(s, NULL, tail); - scr_str_release(tail); - if (scr_exc_pending()) return; - } scr_net_fire0_this(&s->end_ls, s, SCR_DYNH_NET_SOCKET); if (scr_exc_pending()) return; if (!s->wr_ending) { @@ -1611,21 +1625,25 @@ static bool scr_net_sock_deliver(ScrNetSocket *s, const char *buf, size_t n) { * next chunk takes the listener path */ return true; } + if (s->encoding != NULL && (s->pipe_dst != NULL || s->data_ls.n > 0)) { + /* the decoder answers the complete characters; a partial sequence + * waits for the next chunk, and an empty answer emits nothing */ + ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n); + chunk->is_buffer = true; + memcpy(chunk->data, buf, n); + ScrStr *text = scr_strdec_write(s->encoding, s->dec_pending, chunk); + s->dec_pending = scr_strdec_next(s->encoding, s->dec_pending, chunk); + scr_bytes_release(chunk); + if (text->len > 0) scr_net_sock_emit_text(s, text); + scr_str_release(text); + return !scr_exc_pending(); + } if (s->pipe_dst) scr_net_sock_write_raw(s->pipe_dst, buf, n); if (s->data_ls.n > 0) { ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n); chunk->is_buffer = true; /* Node delivers a Buffer */ memcpy(chunk->data, buf, n); - if (s->encoding != NULL) { - /* the decoder answers the complete characters; a partial sequence - * waits for the next chunk, and an empty answer emits nothing */ - ScrStr *text = scr_strdec_write(s->encoding, s->dec_pending, chunk); - s->dec_pending = scr_strdec_next(s->encoding, s->dec_pending, chunk); - if (text->len > 0) scr_net_sock_fire_data(s, NULL, text); - scr_str_release(text); - } else { - scr_net_sock_fire_data(s, chunk, NULL); - } + scr_net_sock_fire_data(s, chunk, NULL); scr_bytes_release(chunk); if (scr_exc_pending()) return false; } diff --git a/tests/corpus/net-socket-encoding-pipe.cjs b/tests/corpus/net-socket-encoding-pipe.cjs new file mode 100644 index 000000000..ff81151d9 --- /dev/null +++ b/tests/corpus/net-socket-encoding-pipe.cjs @@ -0,0 +1,44 @@ +// socket.pipe(destination) after socket.setEncoding(enc) writes the +// decoded strings to the destination, as Node's pipe writes each string +// chunk: the destination receives their UTF-8 bytes, a split character +// arrives whole, and a partial character held at EOF is written before the +// destination ends. +"use strict"; +const net = require("net"); + +const source = net.createServer((socket) => { + socket.on("error", () => {}); + socket.write(Buffer.from([0x61, 0xc3])); + setTimeout(() => socket.end(Buffer.from([0xa9, 0x62, 0xe2, 0x82])), 50); +}); + +const received = new Map(); +const collector = net.createServer((socket) => { + const chunks = []; + let label = null; + socket.on("data", (chunk) => chunks.push(chunk)); + socket.on("end", () => { + const all = Buffer.concat(chunks); + const newline = all.indexOf(0x0a); + label = all.subarray(0, newline).toString(); + received.set(label, all.subarray(newline + 1).toString("hex")); + if (received.size === encodings.length) { + for (const name of encodings) console.log(name, received.get(name)); + source.close(); + collector.close(); + } + }); +}); + +const encodings = ["utf8", "hex", "base64", "latin1", "ucs2"]; +source.listen(0, "127.0.0.1", () => { + collector.listen(0, "127.0.0.1", () => { + for (const encoding of encodings) { + const destination = net.connect(collector.address().port, "127.0.0.1"); + destination.write(encoding + "\n"); + const client = net.connect(source.address().port, "127.0.0.1"); + client.setEncoding(encoding); + client.pipe(destination); + } + }); +}); diff --git a/tests/harness/corpus-costs/net-socket-encoding-pipe.cjs.json b/tests/harness/corpus-costs/net-socket-encoding-pipe.cjs.json new file mode 100644 index 000000000..030fd6454 --- /dev/null +++ b/tests/harness/corpus-costs/net-socket-encoding-pipe.cjs.json @@ -0,0 +1 @@ +{ "plain": 2100, "san": 3200 } diff --git a/tests/harness/node-parallel-runner.test.ts b/tests/harness/node-parallel-runner.test.ts index 9caebd258..22c44f39d 100644 --- a/tests/harness/node-parallel-runner.test.ts +++ b/tests/harness/node-parallel-runner.test.ts @@ -59,3 +59,50 @@ test("a relative --node path links the checkout's fixtures and common helpers in ); expect(existsSync(join(out, "tree", "test", "common", "tmpdir.js"))).toBe(true); }); + +test("concurrent files get their own test/common tmpdir", () => { + const checkout = join(scratch, "node-tmpdir"); + mkdirSync(join(checkout, "test", "parallel"), { recursive: true }); + mkdirSync(join(checkout, "test", "fixtures"), { recursive: true }); + mkdirSync(join(checkout, "test", "common"), { recursive: true }); + for (const helper of ["fixtures.js", "dns.js", "internet.js"]) + writeFileSync(join(checkout, "test", "common", helper), "module.exports = {};\n"); + // Each file refreshes the tmpdir, writes a marker and reads it back after + // the other file has refreshed its own: with a shared directory the + // marker is gone and the file exits with 1. + const body = `const fs = require("fs"); +const tmpdir = require("../common/tmpdir"); +tmpdir.refresh(); +fs.writeFileSync(tmpdir.resolve("marker"), __filename); +setTimeout(() => { + process.exit(fs.existsSync(tmpdir.resolve("marker")) ? 0 : 1); +}, 500); +`; + for (const name of ["test-tmp-a.js", "test-tmp-b.js"]) + writeFileSync(join(checkout, "test", "parallel", name), body); + const failingCli = join(scratch, "failing-cli.mjs"); + writeFileSync(failingCli, "process.exit(1);\n"); + const out = join(scratch, "out-tmpdir"); + const result = spawnSync( + process.execPath, + [ + join(repo, "tests/node-parallel/run.mjs"), + "--node", + checkout, + "--pattern", + "^test-tmp-", + "--out", + out, + "--cli", + failingCli, + "--jobs", + "2", + ], + { cwd: scratch, encoding: "utf8" }, + ); + expect(result.status, result.stderr).toBe(0); + const results = JSON.parse(readFileSync(join(out, "results.json"), "utf8")) as { + nodeExit: number; + }[]; + expect(results.map((r) => r.nodeExit)).toEqual([0, 0]); +}); diff --git a/tests/node-parallel/README.md b/tests/node-parallel/README.md index f24f6b29e..664ee41cc 100644 --- a/tests/node-parallel/README.md +++ b/tests/node-parallel/README.md @@ -18,6 +18,6 @@ Options: - `--cli`: the scriptc CLI entry (default `packages/cli/dist/main.js`). - `--jobs`: concurrent files (default 4). -Node runs each file with its `// Flags:` line. Results are written to `/results.json` and `/results.md`. +Node runs each file with its `// Flags:` line. Each job runs its files with its own `TEST_THREAD_ID`, as Node's test runner does, so `test/common/tmpdir` gives concurrent files different directories. Results are written to `/results.json` and `/results.md`. Before a lane relies on the minimal common, run its files under Node in `/tree/test/parallel`: they should all pass there. diff --git a/tests/node-parallel/run.mjs b/tests/node-parallel/run.mjs index dee37c10f..02f967034 100644 --- a/tests/node-parallel/run.mjs +++ b/tests/node-parallel/run.mjs @@ -89,11 +89,14 @@ const firstCause = (text) => .trim() .slice(0, 300); -async function one(file) { +// Each job runs its files with its own TEST_THREAD_ID, as Node's test runner +// does, so test/common/tmpdir gives concurrent files different directories. +async function one(file, job) { + const env = { ...process.env, TEST_THREAD_ID: String(job) }; const dir = join(testRoot, "parallel"); const source = readFileSync(join(dir, file), "utf8"); const flags = (/^\/\/ Flags: (.*)$/m.exec(source)?.[1] ?? "").split(/\s+/).filter(Boolean); - const node = await run(process.execPath, [...flags, file], { cwd: dir, timeout: 120_000 }); + const node = await run(process.execPath, [...flags, file], { cwd: dir, env, timeout: 120_000 }); const exe = join(out, "bin", `${file}.exe`); mkdirSync(join(out, "bin"), { recursive: true }); const build = await run(process.execPath, [values.cli, "build", file, "-o", exe], { @@ -106,7 +109,7 @@ async function one(file) { result.cause = firstCause(build.stdout + build.stderr); return result; } - const native = await run(exe, [], { cwd: dir, timeout: 120_000 }); + const native = await run(exe, [], { cwd: dir, env, timeout: 120_000 }); result.exit = termination(native); result.verdict = sameTermination(node, native) ? "pass" : "fail"; if (result.verdict !== "pass") result.cause = firstCause(native.stderr + native.stdout); @@ -116,9 +119,9 @@ async function one(file) { const results = []; const queue = [...files]; await Promise.all( - Array.from({ length: Number(values.jobs) }, async () => { + Array.from({ length: Number(values.jobs) }, async (_, job) => { for (let file = queue.shift(); file !== undefined; file = queue.shift()) { - const result = await one(file); + const result = await one(file, job); results.push(result); console.log(`${result.verdict.padEnd(8)} ${file}${result.cause ? ` ${result.cause}` : ""}`); }