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}` : ""}`); }