diff --git a/crates/perry-runtime/src/node_stream_readwrite.rs b/crates/perry-runtime/src/node_stream_readwrite.rs index 8d41581e85..b2b2200a51 100644 --- a/crates/perry-runtime/src/node_stream_readwrite.rs +++ b/crates/perry-runtime/src/node_stream_readwrite.rs @@ -1442,6 +1442,22 @@ pub(super) fn maybe_emit_default_read_error(stream: f64) { destroy_stream(stream, readable_default_read_error()); } +/// Test helper: make `stream` behave like a manually-driven Readable that was +/// constructed with a (no-op) `_read`. #2441 made a *bare* Readable (one with +/// no `_read`) raise `ERR_METHOD_NOT_IMPLEMENTED` and self-destroy on the first +/// read — which is Node-correct (Node requires a `_read`). Tests that drive a +/// stream purely via `push()` clear that marker so they exercise their intended +/// push/flow/end lifecycle without tripping the error, exactly as real code +/// would by passing `{ read() {} }` to the constructor. +#[cfg(test)] +pub(crate) fn test_install_manual_read(stream: f64) { + set_hidden_value( + stream, + hidden_default_read_error_key(), + f64::from_bits(TAG_FALSE), + ); +} + pub(super) fn is_single_chunk_value(value: f64) -> bool { let jsval = JSValue::from_bits(value.to_bits()); if jsval.is_any_string() { diff --git a/crates/perry-runtime/src/node_stream_state_tests.rs b/crates/perry-runtime/src/node_stream_state_tests.rs index b30b4d49d1..4cc4fb0547 100644 --- a/crates/perry-runtime/src/node_stream_state_tests.rs +++ b/crates/perry-runtime/src/node_stream_state_tests.rs @@ -28,6 +28,7 @@ fn fresh_streams_expose_destroyed_false() { #[test] fn readable_lifecycle_flags_reflect_ended_state() { let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); + test_install_manual_read(stream); let handle = raw_ptr_from_value(stream) as i64; let obj = raw_ptr_from_value(stream) as *const ObjectHeader; diff --git a/crates/perry-runtime/src/node_stream_tests.rs b/crates/perry-runtime/src/node_stream_tests.rs index 51fe815258..54b4aafa28 100644 --- a/crates/perry-runtime/src/node_stream_tests.rs +++ b/crates/perry-runtime/src/node_stream_tests.rs @@ -216,6 +216,7 @@ fn readable_set_encoding_emits_buffer_chunks_as_strings() { READABLE_DATA_STRING_FLAGS.with(|flags| flags.borrow_mut().clear()); let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); + test_install_manual_read(stream); let handle = raw_ptr_from_value(stream) as i64; js_node_stream_method_set_encoding(handle, string_value("base64")); diff --git a/crates/perry-runtime/src/node_stream_tests_extra.rs b/crates/perry-runtime/src/node_stream_tests_extra.rs index 6f8401a998..705602deac 100644 --- a/crates/perry-runtime/src/node_stream_tests_extra.rs +++ b/crates/perry-runtime/src/node_stream_tests_extra.rs @@ -315,6 +315,7 @@ fn pause_resume_track_readable_flowing_and_events() { STREAM_EVENT_ORDER.with(|events| events.borrow_mut().clear()); let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); + test_install_manual_read(stream); let handle = raw_ptr_from_value(stream) as i64; let obj = raw_ptr_from_value(stream) as *const ObjectHeader; let pause = js_object_get_field_by_name_f64(obj, hidden_key(b"pause")); @@ -378,6 +379,7 @@ fn readable_push_emits_data_with_stream_this_and_deferred_end() { READABLE_END_COUNT.with(|count| *count.borrow_mut() = 0); let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); + test_install_manual_read(stream); let handle = raw_ptr_from_value(stream) as i64; let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index e314c10cee..d214b36a45 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -1233,6 +1233,7 @@ mod tests { #[test] fn stream_promises_finished_rejects_later_destroy_error() { let stream = crate::node_stream::js_node_stream_readable_new(undefined_value()); + crate::node_stream::test_install_manual_read(stream); let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); let promise = promise_ptr(promise_value); diff --git a/scripts/check_file_size.sh b/scripts/check_file_size.sh index 7b61cf8291..a201e08556 100755 --- a/scripts/check_file_size.sh +++ b/scripts/check_file_size.sh @@ -102,6 +102,12 @@ crates/perry-runtime/src/object/native_module.rs # ObjectHeader) tipped it over. Splitting the two towers into sibling # modules is tracked under #1435. crates/perry-runtime/src/builtins/formatting.rs +# WHATWG Streams (ReadableStream/WritableStream/TransformStream + reader/ +# writer/controller dispatch). Crossed the limit at 2088 LOC after the +# web-stream correctness batch (#2450 pipeTo preventClose, #2455 BYOB-on- +# non-byte reject, #2460 reserved-type reject) landed on main without a +# split. Splitting per stream-kind family is tracked under #2472. +crates/perry-stdlib/src/streams.rs EOF )