From a4cf1b29a76cdb597725d5de15cb3ccb10bdae03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 22 Aug 2026 15:39:10 +0200 Subject: [PATCH] test: migrate loose regressions to parity corpus --- .../src/object/global_this_webassembly.rs | 2 +- .../chained_cross_module_getter/lib.ts | 12 + .../parity_5891/cross_module_getter/lib.ts | 5 + .../export_const_static_alias/barrel.ts | 2 + .../export_const_static_alias/types.ts | 6 + .../imported_accessor/base_texture.ts | 31 +++ .../imported_function_static/res.ts | 9 + .../parity_5891/namespace_const/util_mod.ts | 6 + .../parity_5891/namespace_import/schema.ts | 7 + .../parity_5891/namespace_reexport/barrel.ts | 4 + .../namespace_reexport/external.ts | 2 + .../parity_5891/namespace_reexport/sub.ts | 1 + .../parity_5891/native_class/data_texture.ts | 6 + .../parity_5891/native_class/source.ts | 4 + .../parity_5891/native_class/texture.ts | 5 + .../parity_5891/object_shorthand/client.ts | 2 + .../parity_5891/type_only_namespace/Data.ts | 1 + .../parity_5891/type_only_namespace/Schema.ts | 3 + .../fixtures/parity_5891/xmod_empty/mid.ts | 2 + .../fixtures/parity_5891/xmod_empty/root.ts | 3 + .../xmod_multilevel/base_object.ts | 13 ++ .../xmod_multilevel/camera_base.ts | 10 + .../xmod_multilevel/perspective_like.ts | 9 + .../fixtures/parity_5891/xmod_zero/base.ts | 4 + .../fixtures/parity_5891/xmod_zero/root.ts | 3 + ...arity_684_type_only_namespace_collision.ts | 6 + ...st_parity_array_property_index_set_4150.ts | 11 + ...ity_async_gen_await_in_conditional_6728.ts | 29 +++ test-files/test_parity_async_semantic_3583.ts | 28 +++ ...parity_async_try_catch_after_await_4036.ts | 33 +++ ..._parity_builtin_static_name_length_3655.ts | 29 +++ ...test_parity_chained_cross_module_getter.ts | 5 + ..._parity_class_expr_capture_refresh_6604.ts | 83 +++++++ ...est_parity_class_field_async_arrow_6728.ts | 19 ++ test-files/test_parity_cross_module_getter.ts | 5 + ..._parity_ctor_method_self_bind_not_field.ts | 33 +-- ..._parity_ctor_return_override_standalone.ts | 14 ++ ...ty_defineproperty_prototype_getter_this.ts | 24 ++ ...rity_derived_constructor_arrow_new_4150.ts | 6 + test-files/test_parity_descriptor_decode.ts | 40 ++++ ...arity_destructuring_iterator_assignment.ts | 36 --- ...ty_effect_number_isfinite_property_path.ts | 16 ++ ..._parity_export_const_static_field_alias.ts | 7 + ...est_parity_fetch_binary_body_round_trip.ts | 12 + ...arity_function_bind_value_reflect_apply.ts | 13 ++ ...ity_headers_handle_field_probe_segfault.ts | 0 ...parity_headers_iterator_handle_segfault.ts | 0 ...arity_imported_function_static_property.ts | 9 + ...imported_inherited_accessor_datatexture.ts | 65 ++++++ ...st_parity_inherited_ctor_arg_forwarding.ts | 18 ++ ...t_parity_inline_closure_capturing_local.ts | 0 .../test_parity_map_set_semantics_3989.ts | 54 +++++ .../test_parity_math_random_bind_4120.ts | 7 + .../test_parity_method_value_snapshot_bind.ts | 31 +++ ...ti_await_throw_no_orphan_rejection_6728.ts | 15 ++ ...est_parity_namespace_const_cross_module.ts | 6 + ...test_parity_namespace_import_enumerable.ts | 11 + .../test_parity_namespace_reexport_binding.ts | 5 + ...t_parity_native_class_semantics_reduced.ts | 14 ++ .../test_parity_native_value_profile.ts | 0 .../test_parity_new_array_index_set_4150.ts | 7 + ...arity_numeric_enum_reverse_mapping_4509.ts | 13 ++ ...st_parity_object_assign_function_source.ts | 12 + ...ity_object_ctor_inherited_proto_methods.ts | 8 + ...rity_object_literal_getter_this_binding.ts | 28 +++ ...arity_object_shorthand_imported_binding.ts | 13 ++ ...arity_optional_chain_double_member_call.ts | 15 ++ test-files/test_parity_proxy_field_ic_miss.ts | 16 ++ .../test_parity_proxy_fused_method_call.ts | 31 +++ ..._parity_proxy_getprototypeof_instanceof.ts | 21 ++ .../test_parity_proxy_symbol_property.ts | 11 + test-files/test_parity_regex_has_indices.ts | 10 + .../test_parity_regex_replace_fn_lookahead.ts | 18 ++ .../test_parity_request_subclass_body.ts | 18 ++ ...est_parity_request_subclass_stream_body.ts | 20 ++ ...t_parity_string_append_surrogate_repair.ts | 22 ++ ...test_parity_subclass_builtin_field_init.ts | 13 ++ ...ity_subclass_own_field_init_after_super.ts | 18 ++ ...test_parity_try_block_no_leak_on_return.ts | 7 + .../test_parity_try_catch_across_await.ts | 32 +++ ...test_parity_undici_from_char_code_apply.ts | 10 + .../test_parity_user_method_named_sort.ts | 7 + ...arity_webassembly_graceful_fail_default.ts | 34 +++ ...xmod_empty_imported_derived_arg_forward.ts | 15 ++ ...ity_xmod_imported_multilevel_ctor_state.ts | 12 + ...est_parity_xmod_imported_zero_arg_super.ts | 9 + test-parity/README.md | 20 ++ ...st_parity_namespace_const_cross_module.txt | 1 + .../test_parity_native_value_profile.txt | 1 + ...rity_numeric_enum_reverse_mapping_4509.txt | 1 + ...test_parity_regex_replace_fn_lookahead.txt | 1 + ...st_parity_request_subclass_stream_body.txt | 4 + ...rity_webassembly_graceful_fail_default.txt | 8 + test-parity/known_failures.json | 12 + .../crypto/create-key-invalid-throws.ts | 10 + .../node-suite/http/post-symbol-handle.ts | 38 ++++ .../node-suite/http2/server-request-value.ts | 7 + ....js => class-expr-capture-refresh-edge.ts} | 0 test-parity/node-suite/process/env/value.ts | 7 + .../stream/prototype-eventemitter-borrow.ts | 27 +++ .../node-suite/tty/write-stream-pipe.ts | 0 .../util/textencoder-hoisted-function-decl.ts | 15 ++ test-parity/node_suite_baseline.json | 32 +-- .../test_684_type_only_namespace_collision.sh | 86 -------- tests/test_chained_cross_module_getter.sh | 82 ------- tests/test_class_expr_capture_refresh_6604.sh | 173 --------------- tests/test_class_expr_capture_refresh_6654.sh | 52 ----- tests/test_cross_module_getter.sh | 66 ------ .../test_crypto_create_key_invalid_throws.sh | 45 ---- tests/test_ctor_return_override_standalone.sh | 46 ---- ...st_defineproperty_prototype_getter_this.sh | 106 --------- tests/test_descriptor_decode_parity.sh | 65 ------ ...st_effect_number_isfinite_property_path.sh | 63 ------ tests/test_export_const_static_field_alias.sh | 49 ----- tests/test_fetch_binary_body_round_trip.sh | 73 ------ .../test_function_bind_value_reflect_apply.sh | 47 ---- ...est_headers_handle_field_probe_segfault.sh | 52 ----- .../test_headers_iterator_handle_segfault.sh | 53 ----- tests/test_http2_server_request_value.sh | 42 ---- tests/test_http_post_symbol_handle.sh | 88 -------- .../test_imported_function_static_property.sh | 56 ----- ...imported_inherited_accessor_datatexture.sh | 208 ------------------ tests/test_inherited_ctor_arg_forwarding.sh | 49 ----- tests/test_inline_closure_capturing_local.sh | 45 ---- .../test_issue_3583_async_semantic_parity.sh | 101 --------- ...t_issue_3655_builtin_static_name_length.sh | 93 -------- .../test_issue_3908_tty_write_stream_pipe.sh | 2 +- tests/test_issue_3989_map_set_semantics.sh | 133 ----------- ..._issue_4036_async_try_catch_after_await.sh | 122 ---------- tests/test_issue_4120_math_random_bind.sh | 59 ----- ...est_issue_4150_array_property_index_set.sh | 62 ------ ...ssue_4150_derived_constructor_arrow_new.sh | 58 ----- tests/test_issue_4150_new_array_index_set.sh | 56 ----- ...issue_4509_numeric_enum_reverse_mapping.sh | 87 -------- ...sue_6728_async_gen_await_in_conditional.sh | 82 ------- ...test_issue_6728_class_field_async_arrow.sh | 76 ------- ...8_multi_await_throw_no_orphan_rejection.sh | 78 ------- .../test_issue_undici_from_char_code_apply.sh | 58 ----- tests/test_method_value_snapshot_bind.sh | 51 ----- tests/test_namespace_const_cross_module.sh | 48 ---- tests/test_namespace_import_enumerable.sh | 64 ------ tests/test_namespace_reexport_binding.sh | 63 ------ tests/test_native_class_semantics_reduced.sh | 91 -------- tests/test_native_value_profile.sh | 42 ---- tests/test_object_assign_function_source.sh | 46 ---- ...est_object_ctor_inherited_proto_methods.sh | 38 ---- ...test_object_literal_getter_this_binding.sh | 69 ------ .../test_object_shorthand_imported_binding.sh | 59 ----- .../test_optional_chain_double_member_call.sh | 88 -------- tests/test_process_env_value.sh | 36 --- tests/test_proxy_field_ic_miss.sh | 66 ------ tests/test_proxy_fused_method_call.sh | 97 -------- tests/test_proxy_getprototypeof_instanceof.sh | 67 ------ tests/test_proxy_symbol_property.sh | 51 ----- tests/test_regex_d_flag.sh | 25 --- tests/test_regex_d_flag_comprehensive.sh | 84 ------- tests/test_regex_replace_fn_lookahead.sh | 52 ----- tests/test_request_subclass_body.sh | 84 ------- tests/test_request_subclass_stream_body.sh | 72 ------ ...st_stream_prototype_eventemitter_borrow.sh | 104 --------- tests/test_string_append_surrogate_repair.sh | 86 -------- tests/test_subclass_builtin_field_init.sh | 49 ----- ...est_subclass_own_field_init_after_super.sh | 54 ----- .../test_textencoder_hoisted_function_decl.sh | 50 ----- tests/test_try_block_no_leak_on_return.sh | 69 ------ tests/test_try_catch_across_await.sh | 123 ----------- tests/test_user_method_named_sort.sh | 44 ---- tests/test_webassembly_graceful_fail.sh | 110 --------- ...xmod_empty_imported_derived_arg_forward.sh | 88 -------- ...est_xmod_imported_multilevel_ctor_state.sh | 124 ----------- tests/test_xmod_imported_zero_arg_super.sh | 80 ------- 171 files changed, 1357 insertions(+), 4970 deletions(-) create mode 100644 test-files/fixtures/parity_5891/chained_cross_module_getter/lib.ts create mode 100644 test-files/fixtures/parity_5891/cross_module_getter/lib.ts create mode 100644 test-files/fixtures/parity_5891/export_const_static_alias/barrel.ts create mode 100644 test-files/fixtures/parity_5891/export_const_static_alias/types.ts create mode 100644 test-files/fixtures/parity_5891/imported_accessor/base_texture.ts create mode 100644 test-files/fixtures/parity_5891/imported_function_static/res.ts create mode 100644 test-files/fixtures/parity_5891/namespace_const/util_mod.ts create mode 100644 test-files/fixtures/parity_5891/namespace_import/schema.ts create mode 100644 test-files/fixtures/parity_5891/namespace_reexport/barrel.ts create mode 100644 test-files/fixtures/parity_5891/namespace_reexport/external.ts create mode 100644 test-files/fixtures/parity_5891/namespace_reexport/sub.ts create mode 100644 test-files/fixtures/parity_5891/native_class/data_texture.ts create mode 100644 test-files/fixtures/parity_5891/native_class/source.ts create mode 100644 test-files/fixtures/parity_5891/native_class/texture.ts create mode 100644 test-files/fixtures/parity_5891/object_shorthand/client.ts create mode 100644 test-files/fixtures/parity_5891/type_only_namespace/Data.ts create mode 100644 test-files/fixtures/parity_5891/type_only_namespace/Schema.ts create mode 100644 test-files/fixtures/parity_5891/xmod_empty/mid.ts create mode 100644 test-files/fixtures/parity_5891/xmod_empty/root.ts create mode 100644 test-files/fixtures/parity_5891/xmod_multilevel/base_object.ts create mode 100644 test-files/fixtures/parity_5891/xmod_multilevel/camera_base.ts create mode 100644 test-files/fixtures/parity_5891/xmod_multilevel/perspective_like.ts create mode 100644 test-files/fixtures/parity_5891/xmod_zero/base.ts create mode 100644 test-files/fixtures/parity_5891/xmod_zero/root.ts create mode 100644 test-files/test_parity_684_type_only_namespace_collision.ts create mode 100644 test-files/test_parity_array_property_index_set_4150.ts create mode 100644 test-files/test_parity_async_gen_await_in_conditional_6728.ts create mode 100644 test-files/test_parity_async_semantic_3583.ts create mode 100644 test-files/test_parity_async_try_catch_after_await_4036.ts create mode 100644 test-files/test_parity_builtin_static_name_length_3655.ts create mode 100644 test-files/test_parity_chained_cross_module_getter.ts create mode 100644 test-files/test_parity_class_expr_capture_refresh_6604.ts create mode 100644 test-files/test_parity_class_field_async_arrow_6728.ts create mode 100644 test-files/test_parity_cross_module_getter.ts rename tests/test_ctor_method_self_bind_not_field.sh => test-files/test_parity_ctor_method_self_bind_not_field.ts (50%) mode change 100755 => 100644 create mode 100644 test-files/test_parity_ctor_return_override_standalone.ts create mode 100644 test-files/test_parity_defineproperty_prototype_getter_this.ts create mode 100644 test-files/test_parity_derived_constructor_arrow_new_4150.ts create mode 100644 test-files/test_parity_descriptor_decode.ts rename tests/test_destructuring_iterator_assignment.sh => test-files/test_parity_destructuring_iterator_assignment.ts (81%) mode change 100755 => 100644 create mode 100644 test-files/test_parity_effect_number_isfinite_property_path.ts create mode 100644 test-files/test_parity_export_const_static_field_alias.ts create mode 100644 test-files/test_parity_fetch_binary_body_round_trip.ts create mode 100644 test-files/test_parity_function_bind_value_reflect_apply.ts rename tests/test_headers_handle_field_probe_segfault.ts => test-files/test_parity_headers_handle_field_probe_segfault.ts (100%) rename tests/test_headers_iterator_handle_segfault.ts => test-files/test_parity_headers_iterator_handle_segfault.ts (100%) create mode 100644 test-files/test_parity_imported_function_static_property.ts create mode 100644 test-files/test_parity_imported_inherited_accessor_datatexture.ts create mode 100644 test-files/test_parity_inherited_ctor_arg_forwarding.ts rename tests/issue_inline_closure_capturing_local.js => test-files/test_parity_inline_closure_capturing_local.ts (100%) create mode 100644 test-files/test_parity_map_set_semantics_3989.ts create mode 100644 test-files/test_parity_math_random_bind_4120.ts create mode 100644 test-files/test_parity_method_value_snapshot_bind.ts create mode 100644 test-files/test_parity_multi_await_throw_no_orphan_rejection_6728.ts create mode 100644 test-files/test_parity_namespace_const_cross_module.ts create mode 100644 test-files/test_parity_namespace_import_enumerable.ts create mode 100644 test-files/test_parity_namespace_reexport_binding.ts create mode 100644 test-files/test_parity_native_class_semantics_reduced.ts rename tests/fixtures/native_value_profile.ts => test-files/test_parity_native_value_profile.ts (100%) create mode 100644 test-files/test_parity_new_array_index_set_4150.ts create mode 100644 test-files/test_parity_numeric_enum_reverse_mapping_4509.ts create mode 100644 test-files/test_parity_object_assign_function_source.ts create mode 100644 test-files/test_parity_object_ctor_inherited_proto_methods.ts create mode 100644 test-files/test_parity_object_literal_getter_this_binding.ts create mode 100644 test-files/test_parity_object_shorthand_imported_binding.ts create mode 100644 test-files/test_parity_optional_chain_double_member_call.ts create mode 100644 test-files/test_parity_proxy_field_ic_miss.ts create mode 100644 test-files/test_parity_proxy_fused_method_call.ts create mode 100644 test-files/test_parity_proxy_getprototypeof_instanceof.ts create mode 100644 test-files/test_parity_proxy_symbol_property.ts create mode 100644 test-files/test_parity_regex_has_indices.ts create mode 100644 test-files/test_parity_regex_replace_fn_lookahead.ts create mode 100644 test-files/test_parity_request_subclass_body.ts create mode 100644 test-files/test_parity_request_subclass_stream_body.ts create mode 100644 test-files/test_parity_string_append_surrogate_repair.ts create mode 100644 test-files/test_parity_subclass_builtin_field_init.ts create mode 100644 test-files/test_parity_subclass_own_field_init_after_super.ts create mode 100644 test-files/test_parity_try_block_no_leak_on_return.ts create mode 100644 test-files/test_parity_try_catch_across_await.ts create mode 100644 test-files/test_parity_undici_from_char_code_apply.ts create mode 100644 test-files/test_parity_user_method_named_sort.ts create mode 100644 test-files/test_parity_webassembly_graceful_fail_default.ts create mode 100644 test-files/test_parity_xmod_empty_imported_derived_arg_forward.ts create mode 100644 test-files/test_parity_xmod_imported_multilevel_ctor_state.ts create mode 100644 test-files/test_parity_xmod_imported_zero_arg_super.ts create mode 100644 test-parity/expected/test_parity_namespace_const_cross_module.txt create mode 100644 test-parity/expected/test_parity_native_value_profile.txt create mode 100644 test-parity/expected/test_parity_numeric_enum_reverse_mapping_4509.txt create mode 100644 test-parity/expected/test_parity_regex_replace_fn_lookahead.txt create mode 100644 test-parity/expected/test_parity_request_subclass_stream_body.txt create mode 100644 test-parity/expected/test_parity_webassembly_graceful_fail_default.txt create mode 100644 test-parity/node-suite/crypto/create-key-invalid-throws.ts create mode 100644 test-parity/node-suite/http/post-symbol-handle.ts create mode 100644 test-parity/node-suite/http2/server-request-value.ts rename test-parity/node-suite/object/{class-expr-capture-refresh-edge.js => class-expr-capture-refresh-edge.ts} (100%) create mode 100644 test-parity/node-suite/process/env/value.ts create mode 100644 test-parity/node-suite/stream/prototype-eventemitter-borrow.ts rename tests/issue_3908_tty_write_stream_pipe.js => test-parity/node-suite/tty/write-stream-pipe.ts (100%) create mode 100644 test-parity/node-suite/util/textencoder-hoisted-function-decl.ts delete mode 100755 tests/test_684_type_only_namespace_collision.sh delete mode 100755 tests/test_chained_cross_module_getter.sh delete mode 100755 tests/test_class_expr_capture_refresh_6604.sh delete mode 100755 tests/test_class_expr_capture_refresh_6654.sh delete mode 100755 tests/test_cross_module_getter.sh delete mode 100755 tests/test_crypto_create_key_invalid_throws.sh delete mode 100755 tests/test_ctor_return_override_standalone.sh delete mode 100755 tests/test_defineproperty_prototype_getter_this.sh delete mode 100755 tests/test_descriptor_decode_parity.sh delete mode 100755 tests/test_effect_number_isfinite_property_path.sh delete mode 100755 tests/test_export_const_static_field_alias.sh delete mode 100755 tests/test_fetch_binary_body_round_trip.sh delete mode 100755 tests/test_function_bind_value_reflect_apply.sh delete mode 100755 tests/test_headers_handle_field_probe_segfault.sh delete mode 100755 tests/test_headers_iterator_handle_segfault.sh delete mode 100755 tests/test_http2_server_request_value.sh delete mode 100755 tests/test_http_post_symbol_handle.sh delete mode 100755 tests/test_imported_function_static_property.sh delete mode 100755 tests/test_imported_inherited_accessor_datatexture.sh delete mode 100755 tests/test_inherited_ctor_arg_forwarding.sh delete mode 100755 tests/test_inline_closure_capturing_local.sh delete mode 100644 tests/test_issue_3583_async_semantic_parity.sh delete mode 100755 tests/test_issue_3655_builtin_static_name_length.sh delete mode 100755 tests/test_issue_3989_map_set_semantics.sh delete mode 100755 tests/test_issue_4036_async_try_catch_after_await.sh delete mode 100755 tests/test_issue_4120_math_random_bind.sh delete mode 100755 tests/test_issue_4150_array_property_index_set.sh delete mode 100755 tests/test_issue_4150_derived_constructor_arrow_new.sh delete mode 100755 tests/test_issue_4150_new_array_index_set.sh delete mode 100755 tests/test_issue_4509_numeric_enum_reverse_mapping.sh delete mode 100755 tests/test_issue_6728_async_gen_await_in_conditional.sh delete mode 100755 tests/test_issue_6728_class_field_async_arrow.sh delete mode 100755 tests/test_issue_6728_multi_await_throw_no_orphan_rejection.sh delete mode 100755 tests/test_issue_undici_from_char_code_apply.sh delete mode 100755 tests/test_method_value_snapshot_bind.sh delete mode 100755 tests/test_namespace_const_cross_module.sh delete mode 100755 tests/test_namespace_import_enumerable.sh delete mode 100755 tests/test_namespace_reexport_binding.sh delete mode 100755 tests/test_native_class_semantics_reduced.sh delete mode 100755 tests/test_native_value_profile.sh delete mode 100755 tests/test_object_assign_function_source.sh delete mode 100755 tests/test_object_ctor_inherited_proto_methods.sh delete mode 100755 tests/test_object_literal_getter_this_binding.sh delete mode 100755 tests/test_object_shorthand_imported_binding.sh delete mode 100755 tests/test_optional_chain_double_member_call.sh delete mode 100755 tests/test_process_env_value.sh delete mode 100755 tests/test_proxy_field_ic_miss.sh delete mode 100755 tests/test_proxy_fused_method_call.sh delete mode 100755 tests/test_proxy_getprototypeof_instanceof.sh delete mode 100755 tests/test_proxy_symbol_property.sh delete mode 100755 tests/test_regex_d_flag.sh delete mode 100755 tests/test_regex_d_flag_comprehensive.sh delete mode 100755 tests/test_regex_replace_fn_lookahead.sh delete mode 100755 tests/test_request_subclass_body.sh delete mode 100755 tests/test_request_subclass_stream_body.sh delete mode 100755 tests/test_stream_prototype_eventemitter_borrow.sh delete mode 100755 tests/test_string_append_surrogate_repair.sh delete mode 100755 tests/test_subclass_builtin_field_init.sh delete mode 100755 tests/test_subclass_own_field_init_after_super.sh delete mode 100755 tests/test_textencoder_hoisted_function_decl.sh delete mode 100755 tests/test_try_block_no_leak_on_return.sh delete mode 100755 tests/test_try_catch_across_await.sh delete mode 100755 tests/test_user_method_named_sort.sh delete mode 100755 tests/test_webassembly_graceful_fail.sh delete mode 100755 tests/test_xmod_empty_imported_derived_arg_forward.sh delete mode 100755 tests/test_xmod_imported_multilevel_ctor_state.sh delete mode 100755 tests/test_xmod_imported_zero_arg_super.sh diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index 5af5a5b5d0..ce27d282a9 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -1151,7 +1151,7 @@ fn install_webassembly_object_property( // Unit tests (#6558). Only the non-throwing arms are exercised here — the // `js_throw` paths longjmp and are covered by the e2e/parity fixtures // (`test-parity/node-suite/globals/webassembly-*.ts`, -// `tests/test_webassembly_graceful_fail.sh`). +// `test-files/test_parity_webassembly_graceful_fail_default.ts`). // ──────────────────────────────────────────────────────────────────────── #[cfg(test)] diff --git a/test-files/fixtures/parity_5891/chained_cross_module_getter/lib.ts b/test-files/fixtures/parity_5891/chained_cross_module_getter/lib.ts new file mode 100644 index 0000000000..8a7388a746 --- /dev/null +++ b/test-files/fixtures/parity_5891/chained_cross_module_getter/lib.ts @@ -0,0 +1,12 @@ +export class Scroll { + private _top: number = 42; + get scrollTop(): number { return this._top; } +} +export class Viewport { + readonly scroll: Scroll; + constructor() { this.scroll = new Scroll(); } +} +export class VM { + readonly viewport: Viewport; + constructor() { this.viewport = new Viewport(); } +} diff --git a/test-files/fixtures/parity_5891/cross_module_getter/lib.ts b/test-files/fixtures/parity_5891/cross_module_getter/lib.ts new file mode 100644 index 0000000000..fed1483f06 --- /dev/null +++ b/test-files/fixtures/parity_5891/cross_module_getter/lib.ts @@ -0,0 +1,5 @@ +export class Box { + field: number = 100; + method(): number { return 200; } + get prop(): number { return 300; } +} diff --git a/test-files/fixtures/parity_5891/export_const_static_alias/barrel.ts b/test-files/fixtures/parity_5891/export_const_static_alias/barrel.ts new file mode 100644 index 0000000000..bd0728d1c7 --- /dev/null +++ b/test-files/fixtures/parity_5891/export_const_static_alias/barrel.ts @@ -0,0 +1,2 @@ +import * as z from "./types.ts"; +export { z }; diff --git a/test-files/fixtures/parity_5891/export_const_static_alias/types.ts b/test-files/fixtures/parity_5891/export_const_static_alias/types.ts new file mode 100644 index 0000000000..a8679843ad --- /dev/null +++ b/test-files/fixtures/parity_5891/export_const_static_alias/types.ts @@ -0,0 +1,6 @@ +export class ZS { + v = 5; + static create = (): ZS => new ZS(); +} +const stringType = ZS.create; +export { stringType as string }; diff --git a/test-files/fixtures/parity_5891/imported_accessor/base_texture.ts b/test-files/fixtures/parity_5891/imported_accessor/base_texture.ts new file mode 100644 index 0000000000..46b6f4ea1f --- /dev/null +++ b/test-files/fixtures/parity_5891/imported_accessor/base_texture.ts @@ -0,0 +1,31 @@ +export const LinearFilter = 1006; +export class BaseTexture { + public source: any; + public version = 0; + public magFilter: number; + public minFilter: number; + public generateMipmaps = true; + public flipY = true; + public unpackAlignment = 4; + constructor( + image: any = null, _mapping?: any, _wrapS?: any, _wrapT?: any, + magFilter: number = LinearFilter, minFilter: number = LinearFilter, + _format?: any, _type?: any, _anisotropy?: any, _colorSpace?: any, + ) { + this.source = { data: image, version: 0 }; + this.magFilter = magFilter; + this.minFilter = minFilter; + } + get image(): any { return this.source.data; } + set image(value: any) { this.source.data = value; } + get readOnlyTag(): string { return "base-read-only"; } + set writeOnlyTag(value: string) { this.source.writeOnlyTag = value; } + set needsUpdate(value: boolean) { + if (value === true) { this.version++; this.source.version++; } + } +} +export class AliasAccessorBase { + public marker: any = "unset"; + get aliasedValue(): any { return this.marker; } + set aliasedValue(value: any) { this.marker = value; } +} diff --git a/test-files/fixtures/parity_5891/imported_function_static/res.ts b/test-files/fixtures/parity_5891/imported_function_static/res.ts new file mode 100644 index 0000000000..8e1f2d1616 --- /dev/null +++ b/test-files/fixtures/parity_5891/imported_function_static/res.ts @@ -0,0 +1,9 @@ +function StripeResource(this: any, stripe: any, deprecated: any) { + if (deprecated) throw new Error("invoked with a second arg"); + this._stripe = stripe; +} +(StripeResource as any).method = function () { return "M"; }; +(StripeResource as any).extend = function () { return "E"; }; +(StripeResource as any).MAX = 42; +(StripeResource as any).prototype = { _stripe: null, path: "", initialize() {} }; +export { StripeResource }; diff --git a/test-files/fixtures/parity_5891/namespace_const/util_mod.ts b/test-files/fixtures/parity_5891/namespace_const/util_mod.ts new file mode 100644 index 0000000000..bf00f63407 --- /dev/null +++ b/test-files/fixtures/parity_5891/namespace_const/util_mod.ts @@ -0,0 +1,6 @@ +export namespace util { + export function fn() { return "FN"; } + export const objectKeys = (obj: any) => Object.keys(obj); + export const arrow = () => "ARROW"; + export const num = 42; +} diff --git a/test-files/fixtures/parity_5891/namespace_import/schema.ts b/test-files/fixtures/parity_5891/namespace_import/schema.ts new file mode 100644 index 0000000000..698e038e4c --- /dev/null +++ b/test-files/fixtures/parity_5891/namespace_import/schema.ts @@ -0,0 +1,7 @@ +export const alpha = { id: 1 }; +export const beta = { id: 2 }; +export const gammaEnum = ["x", "y"] as const; +export function helper() { return "H"; } +export type Alpha = typeof alpha; +export type Beta = typeof beta; +export interface Thing { id: number } diff --git a/test-files/fixtures/parity_5891/namespace_reexport/barrel.ts b/test-files/fixtures/parity_5891/namespace_reexport/barrel.ts new file mode 100644 index 0000000000..36d6f0ed87 --- /dev/null +++ b/test-files/fixtures/parity_5891/namespace_reexport/barrel.ts @@ -0,0 +1,4 @@ +import * as z from "./external.ts"; +export * from "./external.ts"; +export { z }; +export default z; diff --git a/test-files/fixtures/parity_5891/namespace_reexport/external.ts b/test-files/fixtures/parity_5891/namespace_reexport/external.ts new file mode 100644 index 0000000000..adfa84577d --- /dev/null +++ b/test-files/fixtures/parity_5891/namespace_reexport/external.ts @@ -0,0 +1,2 @@ +export * from "./sub.ts"; +export const coerce = { number: () => 42 }; diff --git a/test-files/fixtures/parity_5891/namespace_reexport/sub.ts b/test-files/fixtures/parity_5891/namespace_reexport/sub.ts new file mode 100644 index 0000000000..87ed7914da --- /dev/null +++ b/test-files/fixtures/parity_5891/namespace_reexport/sub.ts @@ -0,0 +1 @@ +export function object() { return "obj"; } diff --git a/test-files/fixtures/parity_5891/native_class/data_texture.ts b/test-files/fixtures/parity_5891/native_class/data_texture.ts new file mode 100644 index 0000000000..ceef04590e --- /dev/null +++ b/test-files/fixtures/parity_5891/native_class/data_texture.ts @@ -0,0 +1,6 @@ +import { TextureLike } from "./texture.ts"; +export class DataTextureLike extends TextureLike { + declare readonly isDataTexture: true; + declare static readonly DEFAULT_IMAGE: unknown; + dataTag = "data-texture"; +} diff --git a/test-files/fixtures/parity_5891/native_class/source.ts b/test-files/fixtures/parity_5891/native_class/source.ts new file mode 100644 index 0000000000..c247b59a33 --- /dev/null +++ b/test-files/fixtures/parity_5891/native_class/source.ts @@ -0,0 +1,4 @@ +export class SourceLike { + version = 0; + set needsUpdate(value: boolean) { if (value === true) this.version++; } +} diff --git a/test-files/fixtures/parity_5891/native_class/texture.ts b/test-files/fixtures/parity_5891/native_class/texture.ts new file mode 100644 index 0000000000..164649b824 --- /dev/null +++ b/test-files/fixtures/parity_5891/native_class/texture.ts @@ -0,0 +1,5 @@ +import { SourceLike } from "./source.ts"; +export class TextureLike { + source = new SourceLike(); + set needsUpdate(value: boolean) { this.source.needsUpdate = value; } +} diff --git a/test-files/fixtures/parity_5891/object_shorthand/client.ts b/test-files/fixtures/parity_5891/object_shorthand/client.ts new file mode 100644 index 0000000000..ea7c078b3f --- /dev/null +++ b/test-files/fixtures/parity_5891/object_shorthand/client.ts @@ -0,0 +1,2 @@ +export const db = { tag: "DBINST", q: () => 7 }; +export const helper = () => "H"; diff --git a/test-files/fixtures/parity_5891/type_only_namespace/Data.ts b/test-files/fixtures/parity_5891/type_only_namespace/Data.ts new file mode 100644 index 0000000000..19931509f5 --- /dev/null +++ b/test-files/fixtures/parity_5891/type_only_namespace/Data.ts @@ -0,0 +1 @@ +export const kindOf = (x: unknown): string => "data:" + typeof x; diff --git a/test-files/fixtures/parity_5891/type_only_namespace/Schema.ts b/test-files/fixtures/parity_5891/type_only_namespace/Schema.ts new file mode 100644 index 0000000000..988a2eb53c --- /dev/null +++ b/test-files/fixtures/parity_5891/type_only_namespace/Schema.ts @@ -0,0 +1,3 @@ +export const kindOf = (_x: unknown): string => { + throw new Error("Schema.kindOf must never be reached at runtime"); +}; diff --git a/test-files/fixtures/parity_5891/xmod_empty/mid.ts b/test-files/fixtures/parity_5891/xmod_empty/mid.ts new file mode 100644 index 0000000000..f956eabb35 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_empty/mid.ts @@ -0,0 +1,2 @@ +import { EffectfulRoot } from "./root.ts"; +export class EmptyMid extends EffectfulRoot {} diff --git a/test-files/fixtures/parity_5891/xmod_empty/root.ts b/test-files/fixtures/parity_5891/xmod_empty/root.ts new file mode 100644 index 0000000000..2cf7b35ea1 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_empty/root.ts @@ -0,0 +1,3 @@ +export class EffectfulRoot { + constructor(value: any) { (this as any).forwarded = value; } +} diff --git a/test-files/fixtures/parity_5891/xmod_multilevel/base_object.ts b/test-files/fixtures/parity_5891/xmod_multilevel/base_object.ts new file mode 100644 index 0000000000..cf6f679bc9 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_multilevel/base_object.ts @@ -0,0 +1,13 @@ +export class MatrixLike { + label: string; + constructor(label: string) { this.label = label; } + decompose() { return "decompose:" + this.label; } +} +export class BaseObject { + constructor() { + Object.defineProperties(this, { hiddenMarker: { value: "hidden", configurable: true } }); + (this as any).plainBase = "base"; + (this as any).matrixWorld = new MatrixLike("world"); + } + updateMatrixWorld(force?: boolean) { return (this as any).matrixWorld.decompose() + ":" + String(force); } +} diff --git a/test-files/fixtures/parity_5891/xmod_multilevel/camera_base.ts b/test-files/fixtures/parity_5891/xmod_multilevel/camera_base.ts new file mode 100644 index 0000000000..5c6a3ec2d1 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_multilevel/camera_base.ts @@ -0,0 +1,10 @@ +import { BaseObject, MatrixLike } from "./base_object.ts"; +export class CameraBase extends BaseObject { + declare matrixWorld: MatrixLike; + declare matrixWorldInverse: MatrixLike; + constructor() { + super(); + (this as any).matrixWorldInverse = new MatrixLike("inverse"); + (this as any).cameraReady = true; + } +} diff --git a/test-files/fixtures/parity_5891/xmod_multilevel/perspective_like.ts b/test-files/fixtures/parity_5891/xmod_multilevel/perspective_like.ts new file mode 100644 index 0000000000..849a2d07e5 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_multilevel/perspective_like.ts @@ -0,0 +1,9 @@ +import { CameraBase } from "./camera_base.ts"; +import { MatrixLike } from "./base_object.ts"; +export class PerspectiveLike extends CameraBase { + constructor(fov: number, aspect: number, near: number, far: number) { + super(); + (this as any).isPerspectiveLike = true; + (this as any).projectionMatrix = new MatrixLike(`projection:${fov}:${aspect}:${near}:${far}`); + } +} diff --git a/test-files/fixtures/parity_5891/xmod_zero/base.ts b/test-files/fixtures/parity_5891/xmod_zero/base.ts new file mode 100644 index 0000000000..fc9731fda8 --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_zero/base.ts @@ -0,0 +1,4 @@ +import { CrossRoot } from "./root.ts"; +export class CrossBase extends CrossRoot { + constructor() { super(); (this as any).baseReady = "base"; } +} diff --git a/test-files/fixtures/parity_5891/xmod_zero/root.ts b/test-files/fixtures/parity_5891/xmod_zero/root.ts new file mode 100644 index 0000000000..5a5d755e6c --- /dev/null +++ b/test-files/fixtures/parity_5891/xmod_zero/root.ts @@ -0,0 +1,3 @@ +export class CrossRoot { + constructor() { (this as any).rootReady = "root"; } +} diff --git a/test-files/test_parity_684_type_only_namespace_collision.ts b/test-files/test_parity_684_type_only_namespace_collision.ts new file mode 100644 index 0000000000..c263b5723f --- /dev/null +++ b/test-files/test_parity_684_type_only_namespace_collision.ts @@ -0,0 +1,6 @@ +import { kindOf } from "./fixtures/parity_5891/type_only_namespace/Data.ts"; +import type * as Schema from "./fixtures/parity_5891/type_only_namespace/Schema.ts"; + +console.log(kindOf(42)); +console.log(kindOf("hi")); +type _Unused = ReturnType; diff --git a/test-files/test_parity_array_property_index_set_4150.ts b/test-files/test_parity_array_property_index_set_4150.ts new file mode 100644 index 0000000000..485935c346 --- /dev/null +++ b/test-files/test_parity_array_property_index_set_4150.ts @@ -0,0 +1,11 @@ +interface State { width: number; mirror: number[] } +function write(state: State, cell: number, value: number): void { state.mirror[cell] = value; } + +const state: State = { width: 20, mirror: new Array(40) }; +write(state, 0, 112); +write(state, 1, 101); +state.mirror[2] = 114; +if (state.mirror[0] !== 112 || state.mirror[1] !== 101 || state.mirror[2] !== 114) { + throw new Error(`indexed writes: ${state.mirror[0]}, ${state.mirror[1]}, ${state.mirror[2]}`); +} +console.log("array property index set ok"); diff --git a/test-files/test_parity_async_gen_await_in_conditional_6728.ts b/test-files/test_parity_async_gen_await_in_conditional_6728.ts new file mode 100644 index 0000000000..0f01399dff --- /dev/null +++ b/test-files/test_parity_async_gen_await_in_conditional_6728.ts @@ -0,0 +1,29 @@ +class EventStream { + queue: string[] = []; + resolve: (() => void) | null = null; + done = false; + push(e: string) { + this.queue.push(e); + if (this.resolve) { const resolve = this.resolve; this.resolve = null; resolve(); } + } + async *[Symbol.asyncIterator](): AsyncGenerator { + while (true) { + if (this.queue.length === 0) { + if (this.done) return; + await new Promise((resolve) => { this.resolve = resolve; }); + } + while (this.queue.length > 0) yield this.queue.shift() as string; + } + } +} +const stream = new EventStream(); +(async () => { + for await (const event of stream) { + console.log("event " + event); + if (event === "last") { console.log("got last"); process.exit(0); } + } +})(); +setTimeout(() => stream.push("working"), 50); +setTimeout(() => stream.push("token"), 100); +setTimeout(() => stream.push("last"), 150); +setTimeout(() => { console.log("GUARD (deadlocked)"); process.exit(2); }, 3000); diff --git a/test-files/test_parity_async_semantic_3583.ts b/test-files/test_parity_async_semantic_3583.ts new file mode 100644 index 0000000000..d9a6372961 --- /dev/null +++ b/test-files/test_parity_async_semantic_3583.ts @@ -0,0 +1,28 @@ +async function returnsValue() { return 42; } +function failDefault(): number { throw "default-boom"; } +function failArrowDefault(): number { throw "arrow-default-boom"; } +async function rejectsDefault(value = failDefault()) { console.log("default body ran"); return value; } +async function throwsNow() { throw "throw-boom"; } +async function lengthOne(a: number, b = 39) { return a + b; } +const arrowLengthOne = async (a: number, b = 39) => a + b; +const rejectsArrowDefault = async (value = failArrowDefault()) => { + console.log("arrow body ran"); + return value; +}; + +returnsValue().then((value) => console.log("return " + value)); +rejectsDefault().then( + () => console.log("default unexpectedly resolved"), + (err) => console.log("default rejected " + err), +); +throwsNow().then( + () => console.log("throw unexpectedly resolved"), + (err) => console.log("throw rejected " + err), +); +rejectsArrowDefault().then( + () => console.log("arrow default unexpectedly resolved"), + (err) => console.log("arrow default rejected " + err), +); +console.log("fn length " + lengthOne.length); +console.log("arrow length " + arrowLengthOne.length); +console.log("sync end"); diff --git a/test-files/test_parity_async_try_catch_after_await_4036.ts b/test-files/test_parity_async_try_catch_after_await_4036.ts new file mode 100644 index 0000000000..bc832c9dde --- /dev/null +++ b/test-files/test_parity_async_try_catch_after_await_4036.ts @@ -0,0 +1,33 @@ +function boom() { throw "call-boom"; } +function syncCatch() { + try { throw "sync-boom"; } catch (e: any) { console.log("sync caught:", e); } +} +async function noPriorAwait() { + try { throw "no-await-boom"; } catch (e: any) { console.log("no-await caught:", e); } +} +async function directPostAwait() { + await 0; + try { throw "direct-boom"; } catch (e: any) { console.log("direct caught:", e); } +} +async function callPostAwait() { + await 0; + try { boom(); } catch (e: any) { console.log("call caught:", e); } +} +async function promiseResolvePostAwait() { + await Promise.resolve(1); + try { throw new Error("promise-boom"); } catch (e: any) { console.log("promise caught:", e.message); } +} +async function rejectedAwaitStillWorks() { + try { + await Promise.reject("reject-boom"); + console.log("reject unexpectedly resolved"); + } catch (e: any) { console.log("reject caught:", e); } +} + +syncCatch(); +await noPriorAwait(); +await directPostAwait(); +await callPostAwait(); +await promiseResolvePostAwait(); +await rejectedAwaitStillWorks(); +console.log("done"); diff --git a/test-files/test_parity_builtin_static_name_length_3655.ts b/test-files/test_parity_builtin_static_name_length_3655.ts new file mode 100644 index 0000000000..e7ef507102 --- /dev/null +++ b/test-files/test_parity_builtin_static_name_length_3655.ts @@ -0,0 +1,29 @@ +let failures = 0; +function check(label: string, actual: any, expected: any): void { + if (actual !== expected) { console.log(label, actual, expected); failures++; } +} +function checkDataDescriptor(label: string, desc: any, value: any): void { + if (desc === undefined) { failures++; return; } + check(label + ".value", desc.value, value); + check(label + ".writable", desc.writable, false); + check(label + ".enumerable", desc.enumerable, false); + check(label + ".configurable", desc.configurable, true); +} +checkDataDescriptor("Array.from.name", Object.getOwnPropertyDescriptor(Array.from, "name"), "from"); +checkDataDescriptor("Array.from.length", Object.getOwnPropertyDescriptor(Array.from, "length"), 1); +check("BigInt.asIntN.length", BigInt.asIntN.length, 2); +check("Reflect.apply.name", Reflect.apply.name, "apply"); +check("Reflect.apply.length", Reflect.apply.length, 3); +checkDataDescriptor("Reflect.apply.length", Object.getOwnPropertyDescriptor(Reflect.apply, "length"), 3); +checkDataDescriptor("Math.max.length", Object.getOwnPropertyDescriptor(Math.max, "length"), 2); +checkDataDescriptor("ArrayBuffer.isView.name", Object.getOwnPropertyDescriptor(ArrayBuffer.isView, "name"), "isView"); +checkDataDescriptor("Array.name", Object.getOwnPropertyDescriptor(Array, "name"), "Array"); +checkDataDescriptor("Array.length", Object.getOwnPropertyDescriptor(Array, "length"), 1); +checkDataDescriptor("Map.name", Object.getOwnPropertyDescriptor(Map, "name"), "Map"); +checkDataDescriptor("Map.length", Object.getOwnPropertyDescriptor(Map, "length"), 0); +checkDataDescriptor("Date.name", Object.getOwnPropertyDescriptor(Date, "name"), "Date"); +checkDataDescriptor("Date.length", Object.getOwnPropertyDescriptor(Date, "length"), 7); +checkDataDescriptor("DataView.name", Object.getOwnPropertyDescriptor(DataView, "name"), "DataView"); +checkDataDescriptor("DataView.length", Object.getOwnPropertyDescriptor(DataView, "length"), 1); +if (failures !== 0) throw new Error("builtin name/length descriptor parity failed"); +console.log("builtin name/length descriptor parity ok"); diff --git a/test-files/test_parity_chained_cross_module_getter.ts b/test-files/test_parity_chained_cross_module_getter.ts new file mode 100644 index 0000000000..9d13d52d4a --- /dev/null +++ b/test-files/test_parity_chained_cross_module_getter.ts @@ -0,0 +1,5 @@ +import { VM } from "./fixtures/parity_5891/chained_cross_module_getter/lib.ts"; +const vm = new VM(); +console.log("chain=" + vm.viewport.scroll.scrollTop); +const scroll = vm.viewport.scroll; +console.log("local=" + scroll.scrollTop); diff --git a/test-files/test_parity_class_expr_capture_refresh_6604.ts b/test-files/test_parity_class_expr_capture_refresh_6604.ts new file mode 100644 index 0000000000..096de45cdb --- /dev/null +++ b/test-files/test_parity_class_expr_capture_refresh_6604.ts @@ -0,0 +1,83 @@ +// Class expressions must capture live bindings assigned after the class. +const wrapVar = function () { + var C = class _C { constructor(x: string) { (this as any).v = helper(x); } }; + var out = { K: C }; + var helper = (s: string) => "var:" + s; + return out; +}; +console.log((new (wrapVar().K)("a") as any).v); + +const wrapLet = function () { + let C = class { constructor(x: string) { (this as any).v = helperL(x); } }; + var box = { K: C }; + var helperL = (s: string) => "let:" + s; + return box; +}; +const wrapConst = function () { + const C = class { constructor(x: string) { (this as any).v = helperC(x); } }; + var box = { K: C }; + var helperC = (s: string) => "const:" + s; + return box; +}; +console.log((new (wrapLet().K)("b") as any).v); +console.log((new (wrapConst().K)("c") as any).v); + +const registry: any = {}; +const register = (name: string, cls: any) => { registry[name] = cls; }; +const wrapArg = function () { + register("K", class { constructor(x: string) { (this as any).v = helperA(x); } }); + var helperA = (s: string) => "arg:" + s; +}; +wrapArg(); +console.log(new registry.K("d").v); + +const __commonJS = (cb: any, mod?: any) => function __require() { + return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +const require_parse_options = __commonJS({ + "parse-options.js"(_exports: any, module: any) { + const emptyOpts = Object.freeze({}); + module.exports = (options: any) => options && typeof options === "object" ? options : emptyOpts; + }, +}); +const require_comparator = __commonJS({ + "comparator.js"(_exports: any, module: any) { + const ANY = Symbol("SemVer ANY"); + const Comparator = class _Comparator { + static get ANY() { return ANY; } + loose: boolean; + value: string; + constructor(comp: any, options?: any) { + options = parseOptions(options); + if (comp instanceof _Comparator) comp = comp.value; + this.loose = !!options.loose; + this.value = String(comp); + } + toString() { return this.value; } + }; + module.exports = Comparator; + const parseOptions = require_parse_options(); + }, +}); +const Comparator = require_comparator(); +const minimum = [new Comparator(">=0.0.0-0")]; +const c2 = new Comparator(">=1.2.3", { loose: true }); +console.log(String(minimum[0]), c2.value, c2.loose); + +const wrapReassign = function () { + var C = class { constructor() { (this as any).v = tag(); } }; + var out = { K: C }; + var tag = () => "first"; + tag = () => "second"; + return out; +}; +console.log((new (wrapReassign().K)() as any).v); + +const mk = function (t: string) { + var prefix = "p" + t; + var C = class { constructor() { (this as any).v = t + ":" + prefix; } }; + return C; +}; +const A = mk("x"); +const B = mk("y"); +console.log((new A() as any).v, (new B() as any).v); diff --git a/test-files/test_parity_class_field_async_arrow_6728.ts b/test-files/test_parity_class_field_async_arrow_6728.ts new file mode 100644 index 0000000000..07607dd053 --- /dev/null +++ b/test-files/test_parity_class_field_async_arrow_6728.ts @@ -0,0 +1,19 @@ +class Session { + steering: string[] = []; + handle = async (event: any) => { + console.log("handle-enter " + event.type); + if (event.type === "msg" && this.steering.length > 0) this.steering.pop(); + await Promise.resolve(1); + console.log("handle-after-await " + event.type); + }; +} +(async () => { + const session = new Session(); + await session.handle({ type: "a" }); + const ref = session.handle; + await ref({ type: "b" }); + const listeners = new Set([session.handle]); + for (const listener of listeners) await listener({ type: "msg" }); + console.log("done"); + process.exit(0); +})(); diff --git a/test-files/test_parity_cross_module_getter.ts b/test-files/test_parity_cross_module_getter.ts new file mode 100644 index 0000000000..3a7014a018 --- /dev/null +++ b/test-files/test_parity_cross_module_getter.ts @@ -0,0 +1,5 @@ +import { Box } from "./fixtures/parity_5891/cross_module_getter/lib.ts"; +const box = new Box(); +console.log("field=" + box.field); +console.log("method=" + box.method()); +console.log("prop=" + box.prop); diff --git a/tests/test_ctor_method_self_bind_not_field.sh b/test-files/test_parity_ctor_method_self_bind_not_field.ts old mode 100755 new mode 100644 similarity index 50% rename from tests/test_ctor_method_self_bind_not_field.sh rename to test-files/test_parity_ctor_method_self_bind_not_field.ts index b8f09ad7c0..6cb9f2c192 --- a/tests/test_ctor_method_self_bind_not_field.sh +++ b/test-files/test_parity_ctor_method_self_bind_not_field.ts @@ -1,29 +1,5 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A constructor `this.method = this.method.bind(this)` is a METHOD OVERRIDE, not a -# new data field. Previously the ctor-body field-inference pass added `method` as an -# inline field (it excluded declared/inherited fields + accessors, but NOT methods), -# so the codegen field branch shadowed the method: `this.method` read the -# uninitialised slot (`undefined`) BEFORE the assignment ran, and `.bind(this)` on it -# threw "Bind must be called on a function". zod's `ZodType` constructor self-binds -# ~20 methods this way, so every `z.string()` / `z.object()` / … threw at construction. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/m.ts" <<'TS' -// Mirrors zod's ZodType: self-bind a batch of methods in the constructor, with a -// subclass instantiated via a static factory. +// Constructor self-binding must override a method, not infer an uninitialized +// data field. This is the shape used by ZodType. abstract class ZodType { _def: any; spa = (this as any).safeParseAsync; @@ -58,8 +34,3 @@ if (s.parse("hi") !== "parsed:hi") throw new Error("parse() => " + s.parse("hi") if (s.optional() !== "optional") throw new Error("optional() => " + s.optional()); if (s.safeParse("x").data !== "parsed:x") throw new Error("safeParse data wrong"); console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/m.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: ctor method self-bind is not a field" diff --git a/test-files/test_parity_ctor_return_override_standalone.ts b/test-files/test_parity_ctor_return_override_standalone.ts new file mode 100644 index 0000000000..8e2ca196a8 --- /dev/null +++ b/test-files/test_parity_ctor_return_override_standalone.ts @@ -0,0 +1,14 @@ +// A constructor's explicit object/function return overrides the allocated this. +class W1 { constructor() { return { kind: "obj" }; } } +const a: any = new W1(); +if (typeof a !== "object" || a.kind !== "obj") throw new Error("W1: " + typeof a + " " + a.kind); + +function makeFn() { const f: any = (x: string) => "F:" + x; f.tag = "T"; return f; } +class W2 { constructor() { return makeFn(); } } +const b: any = new W2(); +if (typeof b !== "function" || b.tag !== "T" || b("x") !== "F:x") throw new Error("W2: " + typeof b); + +class P { x: number; constructor(v: number) { this.x = v; } } +const p = new P(5); +if (p.x !== 5) throw new Error("P: " + p.x); +console.log("OK"); diff --git a/test-files/test_parity_defineproperty_prototype_getter_this.ts b/test-files/test_parity_defineproperty_prototype_getter_this.ts new file mode 100644 index 0000000000..22748ada9e --- /dev/null +++ b/test-files/test_parity_defineproperty_prototype_getter_this.ts @@ -0,0 +1,24 @@ +let failures = 0; +class A { _x = 42; } +Object.defineProperty(A.prototype, "px", { get() { return (this as any)._x; } }); +const a: any = new A(); +if (a.px !== 42) { console.log("FAIL: prototype getter this"); failures++; } + +class L { + _readableState: any; + constructor() { this._readableState = { pipes: null }; } +} +Object.defineProperty(L.prototype, "transports", { + configurable: false, + enumerable: true, + get() { + const { pipes } = (this as any)._readableState; + return !Array.isArray(pipes) ? [pipes].filter(Boolean) : pipes; + }, +}); +const l: any = new L(); +let result: any; +try { result = l.transports; } catch { failures++; } +if (JSON.stringify(result) !== "[]") failures++; +if (failures !== 0) throw new Error("defineProperty prototype getter this-binding regression failed"); +console.log("defineProperty proto getter this ok"); diff --git a/test-files/test_parity_derived_constructor_arrow_new_4150.ts b/test-files/test_parity_derived_constructor_arrow_new_4150.ts new file mode 100644 index 0000000000..60e5e319b0 --- /dev/null +++ b/test-files/test_parity_derived_constructor_arrow_new_4150.ts @@ -0,0 +1,6 @@ +class Base {} +class Cache extends Base { constructor() { super(); } } +const factory = () => new Cache(); +const value = factory(); +if (!(value instanceof Cache)) throw new Error("expected Cache instance"); +console.log("derived constructor arrow new ok"); diff --git a/test-files/test_parity_descriptor_decode.ts b/test-files/test_parity_descriptor_decode.ts new file mode 100644 index 0000000000..b268dd690e --- /dev/null +++ b/test-files/test_parity_descriptor_decode.ts @@ -0,0 +1,40 @@ +const show = (o: any, k: string) => { + const d = Object.getOwnPropertyDescriptor(o, k)!; + console.log(k, d.value, typeof d.get, d.writable, d.enumerable, d.configurable); +}; +const a: any = {}; +Object.defineProperty(a, "p1", { value: 1 }); +Object.defineProperty(a, "p2", { value: 2, writable: true, enumerable: true, configurable: true }); +Object.defineProperty(a, "p3", { enumerable: true, get: () => 3 }); +show(a, "p1"); show(a, "p2"); show(a, "p3"); console.log("p3-read", a.p3); + +const dproto = { value: 42, enumerable: true }; +Object.defineProperty(a, "inh", Object.create(dproto)); +show(a, "inh"); +let fired = 0; +const dacc = { get value() { fired++; return 7; }, enumerable: true }; +Object.defineProperty(a, "acc", dacc); +show(a, "acc"); console.log("getter-fired", fired >= 1); +class DescLike { get value() { return 9; } } +Object.defineProperty(a, "cls", new DescLike()); +show(a, "cls"); + +(Object.prototype as any).enumerable = true; +Object.defineProperty(a, "pol", { value: 5 }); +show(a, "pol"); +delete (Object.prototype as any).enumerable; +Object.defineProperty(a, "unpol", { value: 6 }); +show(a, "unpol"); + +const b: any = {}; +Object.defineProperty(b, "t", { value: 1, writable: true, enumerable: true, configurable: true }); +Object.defineProperty(b, "t", { get: () => 2, configurable: true }); +show(b, "t"); console.log("t-read", b.t); +Object.defineProperty(b, "t", { value: 3 }); +show(b, "t"); + +const c: any = { x: 1 }; +Object.freeze(c); +let threw = false; +try { Object.defineProperty(c, "x", { value: 2 }); } catch { threw = true; } +console.log("frozen-throws", threw, c.x); diff --git a/tests/test_destructuring_iterator_assignment.sh b/test-files/test_parity_destructuring_iterator_assignment.ts old mode 100755 new mode 100644 similarity index 81% rename from tests/test_destructuring_iterator_assignment.sh rename to test-files/test_parity_destructuring_iterator_assignment.ts index 13f2b18617..7fe8b0b0ba --- a/tests/test_destructuring_iterator_assignment.sh +++ b/test-files/test_parity_destructuring_iterator_assignment.ts @@ -1,21 +1,3 @@ -#!/bin/bash -# Regression: destructuring assignment must match iterator-close and -# target-evaluation order from Test262's assignment/destructuring bucket. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="${PERRY_BIN:-$SCRIPT_DIR/../target/release/perry}" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' function check(value: boolean, label: string) { if (!value) { throw label; @@ -158,21 +140,3 @@ check(closeCount === 1, "iterator return called after abrupt default"); check(caught === "default", "original abrupt completion preserved: " + caught); console.log("ok"); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null -set +e -RUN_OUTPUT=$(./test_bin 2>&1) -RUN_STATUS=$? -set -e - -if [ "$RUN_STATUS" -eq 0 ] && [ "$RUN_OUTPUT" = "ok" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: unexpected output" -echo "Status: $RUN_STATUS" -echo "$RUN_OUTPUT" -exit 1 diff --git a/test-files/test_parity_effect_number_isfinite_property_path.ts b/test-files/test_parity_effect_number_isfinite_property_path.ts new file mode 100644 index 0000000000..708d46a361 --- /dev/null +++ b/test-files/test_parity_effect_number_isfinite_property_path.ts @@ -0,0 +1,16 @@ +let failures = 0; +function check(label: string, actual: any, expected: any): void { + if (actual !== expected) { + console.log(label + ": expected " + String(expected) + ", got " + String(actual)); + failures++; + } +} + +check("direct finite", Number.isFinite(1), true); +check("direct infinity", Number.isFinite(Infinity), false); +check("direct string", Number.isFinite("1"), false); +check("global finite", globalThis.Number.isFinite(1), true); +check("global infinity", globalThis.Number.isFinite(Infinity), false); +check("global string", globalThis.Number.isFinite("1"), false); +if (failures !== 0) throw new Error("Number.isFinite property-path parity failed"); +console.log("effect Number.isFinite property path ok"); diff --git a/test-files/test_parity_export_const_static_field_alias.ts b/test-files/test_parity_export_const_static_field_alias.ts new file mode 100644 index 0000000000..2aa1a7a94e --- /dev/null +++ b/test-files/test_parity_export_const_static_field_alias.ts @@ -0,0 +1,7 @@ +import { z } from "./fixtures/parity_5891/export_const_static_alias/barrel.ts"; +import { string as direct } from "./fixtures/parity_5891/export_const_static_alias/types.ts"; +const a = (z as any).string(); +if (!a || (a as any).v !== 5) throw new Error("via namespace: " + JSON.stringify(a)); +const b = (direct as any)(); +if (!b || (b as any).v !== 5) throw new Error("direct import: " + JSON.stringify(b)); +console.log("OK"); diff --git a/test-files/test_parity_fetch_binary_body_round_trip.ts b/test-files/test_parity_fetch_binary_body_round_trip.ts new file mode 100644 index 0000000000..13c8ce6775 --- /dev/null +++ b/test-files/test_parity_fetch_binary_body_round_trip.ts @@ -0,0 +1,12 @@ +async function main() { + const bin = new Uint8Array([0xff, 0xfe, 0x00, 0x80, 0x50, 0x4e, 0x47]); + const ab = await new Blob([bin]).arrayBuffer(); + console.log("blob.arrayBuffer:", Array.from(new Uint8Array(ab)).join(",")); + const res = new Response('{"msg":"héllo","n":42}'); + console.log("res.text:", await res.text()); + const json = await new Response('{"msg":"héllo","n":42}').json(); + console.log("res.json:", (json as any).msg, (json as any).n); + const ab2 = await new Response("ABC").arrayBuffer(); + console.log("res.arrayBuffer:", Array.from(new Uint8Array(ab2)).join(",")); +} +main().catch((e) => console.log("ERR:", e?.message ?? e)); diff --git a/test-files/test_parity_function_bind_value_reflect_apply.ts b/test-files/test_parity_function_bind_value_reflect_apply.ts new file mode 100644 index 0000000000..226758b78a --- /dev/null +++ b/test-files/test_parity_function_bind_value_reflect_apply.ts @@ -0,0 +1,13 @@ +// Function.prototype.bind must work when invoked as a value. +const bind = Function.prototype.bind; +const call = Function.prototype.call; +const indexOf = String.prototype.indexOf; +const boundCall: any = (Reflect as any).apply(bind, call, [indexOf]); +if (typeof boundCall !== "function") throw new Error("not a function: " + typeof boundCall); +if (boundCall("hello", "l") !== 2) throw new Error("uncurry: " + boundCall("hello", "l")); + +function add(a: number, b: number) { return a + b; } +const g: any = (bind as any).apply(add, [null, 10]); +if (typeof g !== "function") throw new Error("bind.apply not fn: " + typeof g); +if (g(5) !== 15) throw new Error("bind.apply partial: " + g(5)); +console.log("OK"); diff --git a/tests/test_headers_handle_field_probe_segfault.ts b/test-files/test_parity_headers_handle_field_probe_segfault.ts similarity index 100% rename from tests/test_headers_handle_field_probe_segfault.ts rename to test-files/test_parity_headers_handle_field_probe_segfault.ts diff --git a/tests/test_headers_iterator_handle_segfault.ts b/test-files/test_parity_headers_iterator_handle_segfault.ts similarity index 100% rename from tests/test_headers_iterator_handle_segfault.ts rename to test-files/test_parity_headers_iterator_handle_segfault.ts diff --git a/test-files/test_parity_imported_function_static_property.ts b/test-files/test_parity_imported_function_static_property.ts new file mode 100644 index 0000000000..c6eefc76dc --- /dev/null +++ b/test-files/test_parity_imported_function_static_property.ts @@ -0,0 +1,9 @@ +import { StripeResource } from "./fixtures/parity_5891/imported_function_static/res.ts"; +if (typeof (StripeResource as any).method !== "function") throw new Error("method"); +if (typeof (StripeResource as any).extend !== "function") throw new Error("extend"); +if ((StripeResource as any).MAX !== 42) throw new Error("MAX"); +if ((StripeResource as any).name !== "StripeResource") throw new Error("name"); +const method: any = (StripeResource as any).method; +const extend: any = (StripeResource as any).extend; +if (method() !== "M" || extend() !== "E") throw new Error("static function call"); +console.log("OK"); diff --git a/test-files/test_parity_imported_inherited_accessor_datatexture.ts b/test-files/test_parity_imported_inherited_accessor_datatexture.ts new file mode 100644 index 0000000000..45e154e758 --- /dev/null +++ b/test-files/test_parity_imported_inherited_accessor_datatexture.ts @@ -0,0 +1,65 @@ +import { AliasAccessorBase, BaseTexture } from "./fixtures/parity_5891/imported_accessor/base_texture.ts"; +const NearestFilter = 1003; +function assert(condition: any, label: string): void { if (!condition) throw new Error(label); } + +class DataTextureLike extends BaseTexture { + declare magFilter: number; + declare minFilter: number; + declare generateMipmaps: boolean; + declare flipY: boolean; + declare unpackAlignment: number; + public isDataTexture = true; + constructor( + data: any = null, width = 1, height = 1, format?: any, type?: any, + mapping?: any, wrapS?: any, wrapT?: any, magFilter: number = NearestFilter, + minFilter: number = NearestFilter, anisotropy?: any, colorSpace?: any, + ) { + super(null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace); + this.image = { data, width, height }; + this.generateMipmaps = false; + this.flipY = false; + this.unpackAlignment = 1; + this.writeOnlyTag = "ctor-setter"; + } +} + +const payload = new Uint8Array(8); +const texture: any = new DataTextureLike(payload, 2, 1); +assert(texture.isDataTexture === true, "subclass field initialized"); +assert(texture instanceof BaseTexture, "instanceof imported base"); +assert(texture.version === 0, "initial version"); +assert(texture.image.data === payload && texture.image.width === 2 && texture.image.height === 1, "image payload"); +assert(texture.readOnlyTag === "base-read-only", "getter-only inherited accessor"); +assert(texture.source.writeOnlyTag === "ctor-setter", "setter-only inherited accessor"); +assert(texture.magFilter === NearestFilter && texture.minFilter === NearestFilter, "filter defaults"); +assert(texture.generateMipmaps === false && texture.flipY === false && texture.unpackAlignment === 1, "field overrides"); +assert(!Object.keys(texture).includes("image"), "accessor write created image slot"); +assert(!Object.keys(texture).includes("readOnlyTag"), "getter created slot"); +assert(!Object.keys(texture).includes("writeOnlyTag"), "setter created slot"); + +class AliasAccessorChild extends AliasAccessorBase {} +const aliasProto: any = AliasAccessorChild.prototype; +aliasProto.aliasedValue = function badAliasPatch() { return "bad"; }; +const aliasInstance: any = new AliasAccessorChild(); +aliasInstance.aliasedValue = "instance-setter"; +assert(aliasInstance.aliasedValue === "instance-setter", "prototype alias write"); +assert(!Object.keys(aliasInstance).includes("aliasedValue"), "prototype alias created slot"); + +class StaticAccessorPollution { + static get constructorSlot(): string { return "static-slot"; } + static set constructorSlot(_value: string) {} + constructor() { (this as any).constructorSlot = "instance-slot"; } +} +const staticPollution: any = new StaticAccessorPollution(); +assert(StaticAccessorPollution.constructorSlot === "static-slot", "static accessor"); +assert(staticPollution.constructorSlot === "instance-slot", "static accessor suppressed field"); +assert(Object.keys(staticPollution).includes("constructorSlot"), "constructor assignment not own"); + +const replacement = { data: payload, width: 4, height: 3 }; +texture.image = replacement; +assert(texture.image === replacement, "inherited setter round-trip"); +texture.writeOnlyTag = "post-constructor"; +assert(texture.source.writeOnlyTag === "post-constructor", "setter-only post-constructor write"); +texture.needsUpdate = true; +assert(texture.version === 1 && texture.source.version === 1, "needsUpdate versions"); +console.log("OK"); diff --git a/test-files/test_parity_inherited_ctor_arg_forwarding.ts b/test-files/test_parity_inherited_ctor_arg_forwarding.ts new file mode 100644 index 0000000000..790205ed07 --- /dev/null +++ b/test-files/test_parity_inherited_ctor_arg_forwarding.ts @@ -0,0 +1,18 @@ +abstract class Base { + _def: any; + constructor(def: any) { this._def = def; } + abstract _p(): any; +} +class Num extends Base { + _p() { return 1; } + addCheck(c: any): Num { return new Num({ ...this._def, checks: [...this._def.checks, c] }); } + static create = (): Num => new Num({ checks: [], tn: "N" }); +} + +const a: any = Num.create(); +if (JSON.stringify(a._def) !== '{"checks":[],"tn":"N"}') throw new Error("create _def: " + JSON.stringify(a._def)); +const b: any = a.addCheck({ kind: "int" }); +if (JSON.stringify(b._def.checks) !== '[{"kind":"int"}]') throw new Error("addCheck _def: " + JSON.stringify(b._def)); +const c: any = a.addCheck({ kind: "int" }).addCheck({ kind: "min" }); +if (c._def.checks.length !== 2) throw new Error("chain: " + JSON.stringify(c._def.checks)); +console.log("OK"); diff --git a/tests/issue_inline_closure_capturing_local.js b/test-files/test_parity_inline_closure_capturing_local.ts similarity index 100% rename from tests/issue_inline_closure_capturing_local.js rename to test-files/test_parity_inline_closure_capturing_local.ts diff --git a/test-files/test_parity_map_set_semantics_3989.ts b/test-files/test_parity_map_set_semantics_3989.ts new file mode 100644 index 0000000000..3c8d7bdc9e --- /dev/null +++ b/test-files/test_parity_map_set_semantics_3989.ts @@ -0,0 +1,54 @@ +function check(value: boolean, label: string) { if (!value) throw label; } + +const mapClear = Object.getOwnPropertyDescriptor(Map.prototype, "clear")!; +check(typeof mapClear.value === "function" && mapClear.enumerable === false, "Map.prototype.clear descriptor"); +const mapSize = Object.getOwnPropertyDescriptor(Map.prototype, "size")!; +check(typeof mapSize.get === "function", "Map.prototype.size getter"); +let mapSizeThrew = false; +try { mapSize.get!.call({}); } catch { mapSizeThrew = true; } +check(mapSizeThrew, "Map.prototype.size brand check"); + +const setSize = Object.getOwnPropertyDescriptor(Set.prototype, "size")!; +check(typeof setSize.get === "function", "Set.prototype.size getter"); +let setSizeThrew = false; +try { setSize.get!.call({}); } catch { setSizeThrew = true; } +check(setSizeThrew, "Set.prototype.size brand check"); + +check(Object.getPrototypeOf(new Map()) === Map.prototype, "Map instance prototype"); +check(Object.getPrototypeOf(new Set()) === Set.prototype, "Set instance prototype"); +check(Map.prototype.constructor === Map && Set.prototype.constructor === Set, "prototype constructors"); +check(new Map([[0, 1]]).size === 1, "Map constructor inserts"); +check(new Set([0]).size === 1, "Set constructor inserts"); + +const originalMapSet = Map.prototype.set; +let mapSetCalls = 0; +Map.prototype.set = function (_k: any, _v: any) { mapSetCalls++; return this; }; +const observedMap = new Map([[1, 2], [3, 4]]); +check(mapSetCalls === 2 && observedMap.size === 0, "Map constructor observable set"); +let mapClosed = false; +Map.prototype.set = function (_k: any, _v: any) { throw "map boom"; }; +const mapIterable: any = {}; +mapIterable[Symbol.iterator] = () => ({ + next: () => ({ done: false, value: [1, 2] }), + return: () => { mapClosed = true; return { done: true }; }, +}); +try { new Map(mapIterable); } catch {} +check(mapClosed, "Map constructor iterator close"); +Map.prototype.set = originalMapSet; + +const originalSetAdd = Set.prototype.add; +let setAddCalls = 0; +Set.prototype.add = function (_v: any) { setAddCalls++; return this; }; +const observedSet = new Set([1, 2]); +check(setAddCalls === 2 && observedSet.size === 0, "Set constructor observable add"); +let setClosed = false; +Set.prototype.add = function (_v: any) { throw "set boom"; }; +const setIterable: any = {}; +setIterable[Symbol.iterator] = () => ({ + next: () => ({ done: false, value: 1 }), + return: () => { setClosed = true; return { done: true }; }, +}); +try { new Set(setIterable); } catch {} +check(setClosed, "Set constructor iterator close"); +Set.prototype.add = originalSetAdd; +console.log("ok"); diff --git a/test-files/test_parity_math_random_bind_4120.ts b/test-files/test_parity_math_random_bind_4120.ts new file mode 100644 index 0000000000..10dcb88782 --- /dev/null +++ b/test-files/test_parity_math_random_bind_4120.ts @@ -0,0 +1,7 @@ +const boundRandom = Math.random.bind(Math); +console.log(typeof boundRandom); +console.log(typeof boundRandom()); + +let threwTypeError = false; +try { Function.prototype.bind.call({}); } catch (error) { threwTypeError = error instanceof TypeError; } +console.log(threwTypeError); diff --git a/test-files/test_parity_method_value_snapshot_bind.ts b/test-files/test_parity_method_value_snapshot_bind.ts new file mode 100644 index 0000000000..8fe667e9e1 --- /dev/null +++ b/test-files/test_parity_method_value_snapshot_bind.ts @@ -0,0 +1,31 @@ +class A { + tag = "a"; + m() { return "M:" + this.tag; } + constructor() { (this as any).m = (this as any).m.bind(this); } +} +if (new A().m() !== "M:a") throw new Error("self-bind own: " + new A().m()); + +class Base { + m() { return "B:" + (this as any).tag; } + constructor() { (this as any).m = (this as any).m.bind(this); } +} +class Child extends Base { tag = "c"; } +if (new Child().m() !== "B:c") throw new Error("self-bind inherited: " + new Child().m()); + +class C { + tag = "t"; + r(): string { const captured = (this as any).m; (this as any).m = "SHADOW"; return captured(); } + m() { return "C:" + this.tag; } +} +if (new C().r() !== "C:t") throw new Error("this-snapshot regressed: " + new C().r()); + +class D { + tag = "d"; + constructor() { (this as any).m = () => "OWN:" + this.tag; } + m() { return "PROTO"; } +} +if (new D().m() !== "OWN:d") throw new Error("arrow override regressed"); + +function plain(this: any, x: number) { return this.base + x; } +if (plain.bind({ base: 100 })(5) !== 105) throw new Error("plain bind"); +console.log("OK"); diff --git a/test-files/test_parity_multi_await_throw_no_orphan_rejection_6728.ts b/test-files/test_parity_multi_await_throw_no_orphan_rejection_6728.ts new file mode 100644 index 0000000000..22b6310ef2 --- /dev/null +++ b/test-files/test_parity_multi_await_throw_no_orphan_rejection_6728.ts @@ -0,0 +1,15 @@ +function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +async function twoAwaitsThenThrow(): Promise { + await sleep(1); + await sleep(1); + throw new Error("boom"); +} +async function callLayer(): Promise { await twoAwaitsThenThrow(); } +async function main(): Promise { + try { + await callLayer(); + console.log("no throw (wrong)"); + } catch (e: any) { console.log("caught " + e?.message); } + console.log("done"); +} +main(); diff --git a/test-files/test_parity_namespace_const_cross_module.ts b/test-files/test_parity_namespace_const_cross_module.ts new file mode 100644 index 0000000000..d558fc2616 --- /dev/null +++ b/test-files/test_parity_namespace_const_cross_module.ts @@ -0,0 +1,6 @@ +import { util } from "./fixtures/parity_5891/namespace_const/util_mod.ts"; +if (util.fn() !== "FN" || util.arrow() !== "ARROW" || util.num !== 42) throw new Error("namespace values"); +if (JSON.stringify(util.objectKeys({ a: 1, b: 2 })) !== '["a","b"]') throw new Error("objectKeys"); +const objectKeys = util.objectKeys; +if (JSON.stringify(objectKeys({ x: 1 })) !== '["x"]') throw new Error("local objectKeys"); +console.log("OK"); diff --git a/test-files/test_parity_namespace_import_enumerable.ts b/test-files/test_parity_namespace_import_enumerable.ts new file mode 100644 index 0000000000..393478ec67 --- /dev/null +++ b/test-files/test_parity_namespace_import_enumerable.ts @@ -0,0 +1,11 @@ +import * as schema from "./fixtures/parity_5891/namespace_import/schema.ts"; +const expected = '["alpha","beta","gammaEnum","helper"]'; +if (JSON.stringify(Object.keys(schema).sort()) !== expected) throw new Error("keys"); +const forIn: string[] = []; +for (const k in schema) forIn.push(k); +if (JSON.stringify(forIn.sort()) !== expected) throw new Error("for-in"); +if (!Object.prototype.hasOwnProperty.call(schema, "alpha")) throw new Error("hasOwn alpha"); +if (Object.prototype.hasOwnProperty.call(schema, "Alpha")) throw new Error("type leaked"); +if (Object.entries(schema).length !== 4) throw new Error("entries"); +if ((schema as any).alpha.id !== 1 || (schema as any).helper() !== "H") throw new Error("member values"); +console.log("OK"); diff --git a/test-files/test_parity_namespace_reexport_binding.ts b/test-files/test_parity_namespace_reexport_binding.ts new file mode 100644 index 0000000000..623cd9fc48 --- /dev/null +++ b/test-files/test_parity_namespace_reexport_binding.ts @@ -0,0 +1,5 @@ +import { z } from "./fixtures/parity_5891/namespace_reexport/barrel.ts"; +if (typeof z !== "object") throw new Error(`typeof z: ${typeof z}`); +if (typeof (z as any).object !== "function" || typeof (z as any).coerce !== "object") throw new Error("members"); +if ((z as any).coerce.number() !== 42 || (z as any).object() !== "obj") throw new Error("calls"); +console.log("OK"); diff --git a/test-files/test_parity_native_class_semantics_reduced.ts b/test-files/test_parity_native_class_semantics_reduced.ts new file mode 100644 index 0000000000..7479704715 --- /dev/null +++ b/test-files/test_parity_native_class_semantics_reduced.ts @@ -0,0 +1,14 @@ +import { DataTextureLike } from "./fixtures/parity_5891/native_class/data_texture.ts"; +function hasOwn(value: any, key: string): boolean { + return Object.prototype.hasOwnProperty.call(value, key); +} +const texture: any = new DataTextureLike(); +if (texture.dataTag !== "data-texture" || texture.source.version !== 0) throw new Error("initial state"); +if (texture.needsUpdate !== undefined) throw new Error("setter-only read"); +texture.needsUpdate = true; +texture.needsUpdate = false; +texture.needsUpdate = true; +if (texture.source.version !== 2) throw new Error("version: " + texture.source.version); +if (hasOwn(texture, "needsUpdate") || hasOwn(texture, "isDataTexture")) throw new Error("type-only/setting field leaked"); +if (hasOwn(DataTextureLike, "DEFAULT_IMAGE")) throw new Error("declare static leaked"); +console.log("OK"); diff --git a/tests/fixtures/native_value_profile.ts b/test-files/test_parity_native_value_profile.ts similarity index 100% rename from tests/fixtures/native_value_profile.ts rename to test-files/test_parity_native_value_profile.ts diff --git a/test-files/test_parity_new_array_index_set_4150.ts b/test-files/test_parity_new_array_index_set_4150.ts new file mode 100644 index 0000000000..dcd5498459 --- /dev/null +++ b/test-files/test_parity_new_array_index_set_4150.ts @@ -0,0 +1,7 @@ +const values = new Array(4); +values[0] = 112; +values[1] = 101; +if (values.length !== 4) throw new Error("expected preserved array length"); +if (values[0] !== 112 || values[1] !== 101) throw new Error(`indexed writes: ${values[0]}, ${values[1]}`); +if (values[2] !== undefined) throw new Error("expected unwritten slot to remain undefined"); +console.log("new Array index set ok"); diff --git a/test-files/test_parity_numeric_enum_reverse_mapping_4509.ts b/test-files/test_parity_numeric_enum_reverse_mapping_4509.ts new file mode 100644 index 0000000000..24392bb36e --- /dev/null +++ b/test-files/test_parity_numeric_enum_reverse_mapping_4509.ts @@ -0,0 +1,13 @@ +enum Color { Red, Green, Blue } +enum Dir { Up = 1, Down } +enum S { A = "aaa", B = "bbb" } +const c: Color = Color.Blue; +if (Color[c] !== "Blue") throw new Error(`reverse dyn: ${Color[c]}`); +if (Color[0] !== "Red" || Color[1] !== "Green" || Color[2] !== "Blue") throw new Error("reverse literal"); +if (Dir[1] !== "Up" || Dir[2] !== "Down") throw new Error("reverse explicit-base"); +if ((Color["Blue"] as number) !== 2 || (Dir["Down"] as number) !== 2) throw new Error("forward computed"); +if (Color.Blue !== 2 || Dir.Down !== 2) throw new Error("forward member"); +if (S.A !== "aaa" || S["B"] !== "bbb") throw new Error("string forward"); +if ((S as any)["aaa"] !== undefined) throw new Error("string reverse"); +if ((Color as any)[99] !== undefined) throw new Error("out-of-range reverse"); +console.log("numeric enum reverse mapping ok"); diff --git a/test-files/test_parity_object_assign_function_source.ts b/test-files/test_parity_object_assign_function_source.ts new file mode 100644 index 0000000000..45996f2e69 --- /dev/null +++ b/test-files/test_parity_object_assign_function_source.ts @@ -0,0 +1,12 @@ +function Super(this: any, a: any) { this._a = a; } +(Super as any).extend = "EXT"; +(Super as any).method = "METH"; +(Super as any).MAX = 100; +(Super as any).prototype = { _a: null, path: "", initialize() {}, foo() { return 1; } }; + +const t: any = {}; +Object.assign(t, Super); +if (t.extend !== "EXT" || t.method !== "METH" || t.MAX !== 100) throw new Error("enumerable statics missing"); +const keys = Object.keys(t).sort(); +if (JSON.stringify(keys) !== '["MAX","extend","method"]') throw new Error("keys: " + JSON.stringify(keys)); +console.log("OK"); diff --git a/test-files/test_parity_object_ctor_inherited_proto_methods.ts b/test-files/test_parity_object_ctor_inherited_proto_methods.ts new file mode 100644 index 0000000000..a71cbaec1e --- /dev/null +++ b/test-files/test_parity_object_ctor_inherited_proto_methods.ts @@ -0,0 +1,8 @@ +const O: any = Object; +if (typeof O.hasOwnProperty !== "function") throw new Error("hasOwnProperty"); +if (typeof O.isPrototypeOf !== "function") throw new Error("isPrototypeOf"); +if (typeof O.propertyIsEnumerable !== "function") throw new Error("propertyIsEnumerable"); +const proto = O.getPrototypeOf({ a: 1 }); +if (O.hasOwnProperty.call(proto, "constructor") !== true) throw new Error("call(constructor)"); +if (O.hasOwnProperty.call(proto, "nope") !== false) throw new Error("call(nope)"); +console.log("OK"); diff --git a/test-files/test_parity_object_literal_getter_this_binding.ts b/test-files/test_parity_object_literal_getter_this_binding.ts new file mode 100644 index 0000000000..6ada9e8a0b --- /dev/null +++ b/test-files/test_parity_object_literal_getter_this_binding.ts @@ -0,0 +1,28 @@ +const k = Symbol("k"); +const proto: any = { + get method() { return (this as any)[k] ? (this as any)[k].m : "GET"; }, + get plusOne() { return (this as any).x + 1; }, +}; +const inst: any = Object.create(proto); +inst[k] = { m: "POST" }; +inst.x = 41; +if (inst.method !== "POST" || inst.plusOne !== 42) throw new Error("instance getter this"); + +const inst2: any = Object.create(proto); +inst2[k] = { m: "PUT" }; +inst2.x = 9; +if (inst2.method !== "PUT" || inst2.plusOne !== 10) throw new Error("second instance getter this"); + +const own: any = { _v: 5, get v() { return (this as any)._v; } }; +if (own.v !== 5) throw new Error("own accessor this"); + +const sproto: any = { set val(v: number) { (this as any)._v = v * 2; }, get val() { return (this as any)._v; } }; +const sinst: any = Object.create(sproto); +sinst.val = 21; +if (sinst.val !== 42) throw new Error("inherited setter this"); + +class A { v = 0; get x() { return (this as any).v + 100; } } +const leaf: any = Object.create(new A()); +leaf.v = 5; +if (leaf.x !== 105) throw new Error("inherited class getter this"); +console.log("OK"); diff --git a/test-files/test_parity_object_shorthand_imported_binding.ts b/test-files/test_parity_object_shorthand_imported_binding.ts new file mode 100644 index 0000000000..ba19081927 --- /dev/null +++ b/test-files/test_parity_object_shorthand_imported_binding.ts @@ -0,0 +1,13 @@ +import { db, helper } from "./fixtures/parity_5891/object_shorthand/client.ts"; +const a: any = { db }; +if (a.db?.tag !== "DBINST") throw new Error("closed-shape"); +const b: any = { db, n: 1 }; +if (b.db?.tag !== "DBINST" || b.n !== 1) throw new Error("sibling"); +const rest = { x: 9 }; +const c: any = { db, helper, ...rest }; +if (c.db?.tag !== "DBINST" || c.helper() !== "H" || c.x !== 9) throw new Error("spread"); +const key = "dyn"; +const d: any = { db, [key]: 5 }; +if (d.db?.tag !== "DBINST" || d.dyn !== 5) throw new Error("computed"); +if (a.db.q() !== 7) throw new Error("method call"); +console.log("OK"); diff --git a/test-files/test_parity_optional_chain_double_member_call.ts b/test-files/test_parity_optional_chain_double_member_call.ts new file mode 100644 index 0000000000..a12e022498 --- /dev/null +++ b/test-files/test_parity_optional_chain_double_member_call.ts @@ -0,0 +1,15 @@ +const cfg: any = {}; +const u: any = undefined; +const obj: any = { a: { arr: [1, 2, 3] } }; +const r1 = cfg?.allowModels?.some((m: string) => m === "x"); +console.log("r1=" + (r1 === undefined ? "undefined" : r1)); +const r2 = u?.missing?.some((x: number) => x > 0); +console.log("r2=" + (r2 === undefined ? "undefined" : r2)); +const r3 = obj?.a?.arr?.some((x: number) => x === 2); +console.log("r3=" + r3); +const r4 = obj?.a?.arr?.map((x: number) => x * 2).join(","); +console.log("r4=" + r4); +const r5 = u?.missing?.foo?.((x: number) => x > 0); +console.log("r5=" + (r5 === undefined ? "undefined" : r5)); +const o2: any = { fn: () => 99 }; +console.log("r6=" + o2?.fn?.()); diff --git a/test-files/test_parity_proxy_field_ic_miss.ts b/test-files/test_parity_proxy_field_ic_miss.ts new file mode 100644 index 0000000000..c64ed57c38 --- /dev/null +++ b/test-files/test_parity_proxy_field_ic_miss.ts @@ -0,0 +1,16 @@ +const target: any = { name: "id", dataType: "string", notNull: true }; +const proxy: any = new Proxy(target, { get(t: any, k: any) { return t[k]; } }); +let acc = ""; +for (let i = 0; i < 1000; i++) acc = `${proxy.name}:${proxy.dataType}:${proxy.notNull}`; +if (acc !== "id:string:true") throw new Error("proxy string read: " + acc); + +const p2: any = new Proxy(target, {}); +if (p2.name !== "id") throw new Error("proxy target forwarding"); +if (proxy.absent !== undefined) throw new Error("absent key"); + +class Column { name = "col"; sql = "SELECT"; } +const p3: any = new Proxy(new Column(), { get(t: any, k: any) { return t[k]; } }); +let result = ""; +for (let i = 0; i < 1000; i++) result = `${p3.name}/${p3.sql}`; +if (result !== "col/SELECT") throw new Error("proxy class field: " + result); +console.log("OK"); diff --git a/test-files/test_parity_proxy_fused_method_call.ts b/test-files/test_parity_proxy_fused_method_call.ts new file mode 100644 index 0000000000..adb783452d --- /dev/null +++ b/test-files/test_parity_proxy_fused_method_call.ts @@ -0,0 +1,31 @@ +function makeForwardingProxy(): any { + const target: any = { kind: "col" }; + target.run = function (v: any) { return v; }; + return new Proxy(target, { get(t: any, k: any) { return t[k]; } }); +} +const p: any = makeForwardingProxy(); +if (p.run("hi") !== "hi") throw new Error("fused get-trap proxy call"); +const f = p.run; +if (f("hi") !== "hi") throw new Error("decomposed proxy call"); + +const target2: any = {}; +target2.echo = function (v: any) { return v; }; +const p2: any = new Proxy(target2, {}); +if (p2.echo("yo") !== "yo") throw new Error("fused no-trap proxy call"); + +const target3: any = { tag: "T" }; +target3.readTag = function (this: any) { return this.tag; }; +const p3: any = new Proxy(target3, { get(t: any, k: any) { return t[k]; } }); +if (p3.readTag() !== "T") throw new Error("proxy this binding"); + +class Base { mapFromDriverValue(v: any) { return v; } } +class Mid extends Base { x = 1; } +class Leaf extends Mid { y = 2; } +const p4: any = new Proxy(new Leaf(), { get(t: any, k: any) { return t[k]; } }); +if (p4.mapFromDriverValue("hi") !== "hi") throw new Error("inherited proxy method"); + +const target5: any = {}; +target5.add3 = (a: number, b: number, c: number) => a + b + c; +const p5: any = new Proxy(target5, { get(t: any, k: any) { return t[k]; } }); +if (p5.add3(1, 2, 3) !== 6) throw new Error("proxy multi-arg call"); +console.log("OK"); diff --git a/test-files/test_parity_proxy_getprototypeof_instanceof.ts b/test-files/test_parity_proxy_getprototypeof_instanceof.ts new file mode 100644 index 0000000000..88f3934f81 --- /dev/null +++ b/test-files/test_parity_proxy_getprototypeof_instanceof.ts @@ -0,0 +1,21 @@ +class Base { static tag = "B"; } +class Mid extends Base {} +class Leaf extends Mid { + n: string; + constructor(n: string) { super(); this.n = n; } +} + +const handler = { get(t: any, k: any) { return t[k]; } }; +const proxy: any = new Proxy(new Leaf("x"), handler); +const proto = Object.getPrototypeOf(proxy); +if (proto?.constructor?.name !== "Leaf") throw new Error("proto.constructor"); +if (!(proxy instanceof Leaf) || !(proxy instanceof Mid) || !(proxy instanceof Base) || !(proxy instanceof Object)) { + throw new Error("proxy instanceof chain"); +} +if (proxy.n !== "x") throw new Error("proxy member forwarding"); + +const proxy2: any = new Proxy(proxy, handler); +if (!(proxy2 instanceof Leaf) || Object.getPrototypeOf(proxy2)?.constructor?.name !== "Leaf") { + throw new Error("nested proxy prototype"); +} +console.log("OK"); diff --git a/test-files/test_parity_proxy_symbol_property.ts b/test-files/test_parity_proxy_symbol_property.ts new file mode 100644 index 0000000000..fced07ff05 --- /dev/null +++ b/test-files/test_parity_proxy_symbol_property.ts @@ -0,0 +1,11 @@ +const tag = Symbol("tag"); +const other = Symbol.for("shared"); +const target: any = { [tag]: "T", [other]: 42, plain: "p" }; +const proxy: any = new Proxy(target, { get(t: any, k: any) { return t[k]; } }); +if (proxy[tag] !== "T" || proxy[other] !== 42 || proxy.plain !== "p") throw new Error("symbol get trap"); +if (new Proxy(target, {})[tag] !== "T") throw new Error("symbol target forwarding"); +class C { [tag] = "instTag"; } +const p3: any = new Proxy(new C(), { get(t: any, k: any) { return t[k]; } }); +if (p3[tag] !== "instTag") throw new Error("instance symbol proxy"); +if (proxy[Symbol("absent")] !== undefined) throw new Error("absent symbol"); +console.log("OK"); diff --git a/test-files/test_parity_regex_has_indices.ts b/test-files/test_parity_regex_has_indices.ts new file mode 100644 index 0000000000..f4dcdd98ad --- /dev/null +++ b/test-files/test_parity_regex_has_indices.ts @@ -0,0 +1,10 @@ +function show(re: RegExp, input: string): void { + const match: any = re.exec(input); + console.log(JSON.stringify(match?.indices)); + console.log(re.hasIndices); +} +show(/hello/d, "say hello world"); +show(/(\w+)@(\w+)/d, "email: test@example"); +show(/(?\d{4})-(?\d{2})/d, "date: 2024-03"); +show(/(\d+)(\.(\d+))?/d, "int: 42"); +show(/test/, "test"); diff --git a/test-files/test_parity_regex_replace_fn_lookahead.ts b/test-files/test_parity_regex_replace_fn_lookahead.ts new file mode 100644 index 0000000000..48eec4a721 --- /dev/null +++ b/test-files/test_parity_regex_replace_fn_lookahead.ts @@ -0,0 +1,18 @@ +function run(re: RegExp, s: string): string[] { + const out: string[] = []; + s.replace(re, (m: string) => { out.push(m); return m; }); + return out; +} +const a = run(/[a-z]+|(?=\.)/g, "ab.cd"); +if (JSON.stringify(a) !== '["ab","cd"]') throw new Error("A: " + JSON.stringify(a)); +const d = run(/(?=\.)/g, "a.b.c"); +if (JSON.stringify(d) !== '["",""]') throw new Error("D: " + JSON.stringify(d)); + +const rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g; +const result: any[] = []; +"%String.prototype.indexOf%".replace(rePropName, function (m: any, num: any, q: any, sub: any) { + result[result.length] = q ? sub : (num || m); + return m; +} as any); +if (JSON.stringify(result) !== '["String","prototype","indexOf"]') throw new Error("rePropName: " + JSON.stringify(result)); +console.log("OK"); diff --git a/test-files/test_parity_request_subclass_body.ts b/test-files/test_parity_request_subclass_body.ts new file mode 100644 index 0000000000..8314a1bd23 --- /dev/null +++ b/test-files/test_parity_request_subclass_body.ts @@ -0,0 +1,18 @@ +const Sub = class extends Request {}; +class Req2 extends Request { + tag: string; + constructor(input: string, init?: RequestInit) { super(input, init); this.tag = "req2"; } +} +async function main() { + const sub = new Sub("http://x/y", { method: "POST", body: "hello" }); + console.log("sub.text type:", typeof sub.text); + console.log("sub instanceof Request:", sub instanceof Request); + console.log("sub.text body:", await sub.text()); + const r2 = new Req2("http://x/z", { method: "POST", body: '{"n":42}' }); + console.log("r2.tag:", r2.tag); + console.log("r2 instanceof Request:", r2 instanceof Request); + console.log("r2.method:", r2.method); + const j = await r2.json(); + console.log("r2.json.n:", (j as any).n); +} +main(); diff --git a/test-files/test_parity_request_subclass_stream_body.ts b/test-files/test_parity_request_subclass_stream_body.ts new file mode 100644 index 0000000000..f50dd3c524 --- /dev/null +++ b/test-files/test_parity_request_subclass_stream_body.ts @@ -0,0 +1,20 @@ +const GlobalRequest = (globalThis as any).Request; +class Req extends GlobalRequest { + constructor(input: any, init?: any) { super(input, init); } +} +async function main() { + const r = new Req("http://x/y", { method: "POST", body: "HI" }); + console.log("typeof r.text:", typeof r.text); + const k = "text"; + console.log("typeof r[k]:", typeof (r as any)[k]); + console.log("literal text:", await r.text()); + const r2 = new Req("http://x/z", { + method: "POST", + body: new ReadableStream({ + start(c: any) { c.enqueue(new TextEncoder().encode("STREAMED")); c.close(); }, + }), + }); + const fn = (r2 as any)["text"]; + console.log("computed stream text:", await fn.call(r2)); +} +main(); diff --git a/test-files/test_parity_string_append_surrogate_repair.ts b/test-files/test_parity_string_append_surrogate_repair.ts new file mode 100644 index 0000000000..479b9cb86d --- /dev/null +++ b/test-files/test_parity_string_append_surrogate_repair.ts @@ -0,0 +1,22 @@ +function show(label: string, s: string): void { + console.log(label + " len=" + s.length + " cp=" + [...s].length + " cp0=" + s.codePointAt(0) + " eq="); +} +const e = "a👋b"; +const hi = e[1], lo = e[2]; +let y = ""; y += hi; y += lo; +show("pluseq", y); +console.log("pluseq-eq", y === "👋", (hi + lo) === y); +let out = ""; +for (let i = 0; i < e.length; i++) out += e[i]; +console.log("rebuild", out === e, [...out].length); +const t = "x😀y👋z🎉w"; +let r = ""; +for (let i = 0; i < t.length; i++) r += t[i]; +console.log("multi", r === t, [...r].length, r.codePointAt(1)); +let lone = ""; lone += hi; lone += "Z"; +console.log("lone", [...lone].length, lone.codePointAt(0), lone.length); +let ascii = ""; +for (let i = 0; i < 500; i++) ascii += "aé"; +console.log("bulk", ascii.length, [...ascii].length); +let split = ""; split += hi; split += "-"; split += lo; +console.log("split", [...split].length, split.codePointAt(0)); diff --git a/test-files/test_parity_subclass_builtin_field_init.ts b/test-files/test_parity_subclass_builtin_field_init.ts new file mode 100644 index 0000000000..82442dbb78 --- /dev/null +++ b/test-files/test_parity_subclass_builtin_field_init.ts @@ -0,0 +1,13 @@ +class A extends Error { + pubv = 42; + #priv = 7; + arr = [1, 2, 3]; + readPub() { return this.pubv; } + readPriv() { return this.#priv; } + readArr() { return this.arr; } +} +const a = new A(); +if (a.readPub() !== 42) throw new Error("pubv: " + a.readPub()); +if (a.readPriv() !== 7) throw new Error("priv: " + a.readPriv()); +if (!a.readArr().includes(2)) throw new Error("arr: " + JSON.stringify(a.readArr())); +console.log("OK"); diff --git a/test-files/test_parity_subclass_own_field_init_after_super.ts b/test-files/test_parity_subclass_own_field_init_after_super.ts new file mode 100644 index 0000000000..4eb3b0d77e --- /dev/null +++ b/test-files/test_parity_subclass_own_field_init_after_super.ts @@ -0,0 +1,18 @@ +abstract class Base { + _def: any; + constructor(def: any) { this._def = def; } + abstract _p(): any; +} +class Leaf extends Base { + _cached: any = null; + _count = 7; + _list: number[] = [1, 2]; + _p() { return 1; } + static create = (): Leaf => new Leaf({ k: 1 }); +} +const o: any = Leaf.create(); +if (o._cached !== null || o._cached === 0) throw new Error("_cached: " + o._cached); +if (o._count !== 7) throw new Error("_count: " + o._count); +if (JSON.stringify(o._list) !== "[1,2]") throw new Error("_list: " + JSON.stringify(o._list)); +if (JSON.stringify(o._def) !== '{"k":1}') throw new Error("_def: " + JSON.stringify(o._def)); +console.log("OK"); diff --git a/test-files/test_parity_try_block_no_leak_on_return.ts b/test-files/test_parity_try_block_no_leak_on_return.ts new file mode 100644 index 0000000000..4d6aa026f5 --- /dev/null +++ b/test-files/test_parity_try_block_no_leak_on_return.ts @@ -0,0 +1,7 @@ +function step(i: number): number { + try { if (i >= 0) return i; return -1; } catch { return -2; } +} +let sum = 0; +for (let i = 0; i < 500; i++) sum += step(i); +console.log("sum=" + sum); +console.log("done"); diff --git a/test-files/test_parity_try_catch_across_await.ts b/test-files/test_parity_try_catch_across_await.ts new file mode 100644 index 0000000000..8d6cf6d163 --- /dev/null +++ b/test-files/test_parity_try_catch_across_await.ts @@ -0,0 +1,32 @@ +async function noThrow(i: number): Promise { + try { await Promise.resolve(i); return i; } catch { return -1; } +} +async function withThrow(i: number): Promise { + try { await Promise.reject(new Error("boom")); return i; } catch { return -1; } +} +let finallyRuns = 0; +async function withFinally(i: number): Promise { + try { await Promise.resolve(i); return i; } finally { finallyRuns++; } +} +async function nested(i: number): Promise { + try { + await Promise.resolve(i); + try { await Promise.reject(new Error("inner")); return 999; } catch { return i; } + } catch { return -2; } +} +async function main(): Promise { + let sumNoThrow = 0; + for (let i = 0; i < 400; i++) sumNoThrow += await noThrow(i); + console.log("noThrow sum=" + sumNoThrow); + let sumThrow = 0; + for (let i = 0; i < 400; i++) sumThrow += await withThrow(i); + console.log("withThrow sum=" + sumThrow); + let sumFinally = 0; + for (let i = 0; i < 400; i++) sumFinally += await withFinally(i); + console.log("withFinally sum=" + sumFinally + " finallyRuns=" + finallyRuns); + let sumNested = 0; + for (let i = 0; i < 400; i++) sumNested += await nested(i); + console.log("nested sum=" + sumNested); + console.log("DONE"); +} +main().catch((e) => console.log("ERR: " + String((e as any)?.message ?? e))); diff --git a/test-files/test_parity_undici_from_char_code_apply.ts b/test-files/test_parity_undici_from_char_code_apply.ts new file mode 100644 index 0000000000..e23160131e --- /dev/null +++ b/test-files/test_parity_undici_from_char_code_apply.ts @@ -0,0 +1,10 @@ +'use strict'; +const bytes = new Uint8Array([72, 101, 108, 108, 111]); +const viaApply = String.fromCharCode.apply(null, bytes as any); +const viaSpread = String.fromCharCode(...bytes); +const min = Math.min.apply(null, new Uint8Array([3, 1, 2]) as any); +if (viaApply !== "Hello" || viaSpread !== "Hello" || min !== 1) { + console.log(`bad:${viaApply}:${viaSpread}:${min}`); + process.exit(1); +} +console.log(`ok:${viaApply}:${min}`); diff --git a/test-files/test_parity_user_method_named_sort.ts b/test-files/test_parity_user_method_named_sort.ts new file mode 100644 index 0000000000..ea89c5101f --- /dev/null +++ b/test-files/test_parity_user_method_named_sort.ts @@ -0,0 +1,7 @@ +class Sorter { + sort(list: any[]) { + return list.sort((a: any, b: any) => (a < b ? -1 : a > b ? 1 : 0)); + } +} +const s = new Sorter(); +console.log(JSON.stringify(s.sort(["1.2.0", "1.0.1", "1.0.0", "2.0.0"]))); diff --git a/test-files/test_parity_webassembly_graceful_fail_default.ts b/test-files/test_parity_webassembly_graceful_fail_default.ts new file mode 100644 index 0000000000..87ee4cafd9 --- /dev/null +++ b/test-files/test_parity_webassembly_graceful_fail_default.ts @@ -0,0 +1,34 @@ +// Resolve the namespace through a computed key so this test deliberately does +// not trigger the compiler's static WebAssembly host auto-linking. It checks +// the default runtime's honest graceful degradation instead. +const WA: any = (globalThis as any)["Web" + "Assembly"]; +const validAdd = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, + 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, + 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, + 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, + 0x0b, +]); +console.log("validate valid:", WA.validate(validAdd)); +try { + await WA.compile(validAdd); + console.log("compile: resolved"); +} catch (e: any) { + console.log("compile rejected:", e instanceof WA.CompileError, e.name); + console.log("mentions api:", e.message.includes("WebAssembly.compile")); + console.log("mentions issue:", e.message.includes("6558")); +} +try { + new WA.Module(validAdd); + console.log("Module: constructed"); +} catch (e: any) { + console.log("Module threw:", e instanceof WA.CompileError, e.name); + console.log("Module mentions issue:", e.message.includes("6558")); +} +async function lazyLoad(bytes: Uint8Array): Promise { + try { return await WA.instantiate(bytes); } catch { return null; } +} +const loaded = await lazyLoad(validAdd); +console.log("loader result:", loaded === null ? "degraded" : "loaded"); +console.log("program continues"); diff --git a/test-files/test_parity_xmod_empty_imported_derived_arg_forward.ts b/test-files/test_parity_xmod_empty_imported_derived_arg_forward.ts new file mode 100644 index 0000000000..6a29965cbc --- /dev/null +++ b/test-files/test_parity_xmod_empty_imported_derived_arg_forward.ts @@ -0,0 +1,15 @@ +import { EmptyMid } from "./fixtures/parity_5891/xmod_empty/mid.ts"; +const direct: any = new EmptyMid("direct"); +console.log("direct instanceof mid", direct instanceof EmptyMid); +console.log("direct forwarded", direct.forwarded); +class ExplicitLeaf extends EmptyMid { + constructor(value: any) { super(value); (this as any).leafReady = "explicit"; } +} +const explicit: any = new ExplicitLeaf("explicit"); +console.log("explicit instanceof mid", explicit instanceof EmptyMid); +console.log("explicit forwarded", explicit.forwarded); +console.log("explicit ready", explicit.leafReady); +class DefaultLeaf extends EmptyMid {} +const defaultLeaf: any = new DefaultLeaf("default"); +console.log("default instanceof mid", defaultLeaf instanceof EmptyMid); +console.log("default forwarded", defaultLeaf.forwarded); diff --git a/test-files/test_parity_xmod_imported_multilevel_ctor_state.ts b/test-files/test_parity_xmod_imported_multilevel_ctor_state.ts new file mode 100644 index 0000000000..981f4dc769 --- /dev/null +++ b/test-files/test_parity_xmod_imported_multilevel_ctor_state.ts @@ -0,0 +1,12 @@ +import { BaseObject } from "./fixtures/parity_5891/xmod_multilevel/base_object.ts"; +import { CameraBase } from "./fixtures/parity_5891/xmod_multilevel/camera_base.ts"; +import { PerspectiveLike } from "./fixtures/parity_5891/xmod_multilevel/perspective_like.ts"; +const camera: any = new PerspectiveLike(50, 1, 0.1, 100); +console.log("isPerspective", camera.isPerspectiveLike === true); +console.log("instanceofCamera", camera instanceof CameraBase); +console.log("instanceofBase", camera instanceof BaseObject); +console.log("projection", camera.projectionMatrix.decompose()); +console.log("plainBase", camera.plainBase); +console.log("matrixWorld", camera.matrixWorld.decompose()); +console.log("update", camera.updateMatrixWorld(false)); +console.log("inverse", camera.matrixWorldInverse.decompose()); diff --git a/test-files/test_parity_xmod_imported_zero_arg_super.ts b/test-files/test_parity_xmod_imported_zero_arg_super.ts new file mode 100644 index 0000000000..daea409b45 --- /dev/null +++ b/test-files/test_parity_xmod_imported_zero_arg_super.ts @@ -0,0 +1,9 @@ +import { CrossBase } from "./fixtures/parity_5891/xmod_zero/base.ts"; +class CrossSub extends CrossBase { + constructor() { super(); (this as any).subReady = "sub"; } +} +const value: any = new CrossSub(); +console.log("instanceof base", value instanceof CrossBase); +console.log("root", value.rootReady); +console.log("base", value.baseReady); +console.log("sub", value.subReady); diff --git a/test-parity/README.md b/test-parity/README.md index f08ccbab10..4a7bc603f1 100644 --- a/test-parity/README.md +++ b/test-parity/README.md @@ -3,6 +3,26 @@ Two gates read this directory. They cover different suites and work differently; the difference is the point. +## Where runtime regression tests belong + +Self-contained programs whose stdout and exit status should match Node belong +in `test-files/test_parity_.ts`. Put supporting modules under +`test-files/fixtures/` and import them from the single top-level entry point; +the parity runner discovers that entry automatically. + +Tests centered on one Node core module belong in +`test-parity/node-suite//`. That tree is discovered recursively, so +each `.ts` file there must be an independently runnable case rather than a +helper module. + +When Perry intentionally differs from Node, commit the Perry output as +`test-parity/expected/.txt`; add an `expected-exit` file when the +expected Perry exit is nonzero. Keep a shell harness only when the assertion +requires behavior the parity runner cannot express, such as inspecting +compiler output, separating stdout from stderr, creating native libraries, +driving a background service, enforcing a hard timeout or liveness contract, +testing symlink layout, or running multiple compiler/GC configurations. + ## `gap_snapshot.json` — the gap suite (per-PR gate) Generated by `scripts/gap_snapshot.py`, consumed by `scripts/run_gap_tests.sh`, diff --git a/test-parity/expected/test_parity_namespace_const_cross_module.txt b/test-parity/expected/test_parity_namespace_const_cross_module.txt new file mode 100644 index 0000000000..d86bac9de5 --- /dev/null +++ b/test-parity/expected/test_parity_namespace_const_cross_module.txt @@ -0,0 +1 @@ +OK diff --git a/test-parity/expected/test_parity_native_value_profile.txt b/test-parity/expected/test_parity_native_value_profile.txt new file mode 100644 index 0000000000..485980b450 --- /dev/null +++ b/test-parity/expected/test_parity_native_value_profile.txt @@ -0,0 +1 @@ +size=24,align=8,sequence=8,length=1,flags=4294967295,sequenceValue=9007199254740991,gainRounded=true,tiny=2:1:255:255:7,narrow=16:2:4:8:-128:65535:-32768:-9007199254740991:-5:65535:-1024:-42,header=7:42:true,rejectedFraction=true,rejectedType=true,rejectedOctet=true,rejectedSignedByte=true,rejectedHalfWord=true,rejectedSignedHalfWord=true,rejectedSignedSize=true diff --git a/test-parity/expected/test_parity_numeric_enum_reverse_mapping_4509.txt b/test-parity/expected/test_parity_numeric_enum_reverse_mapping_4509.txt new file mode 100644 index 0000000000..7311f47967 --- /dev/null +++ b/test-parity/expected/test_parity_numeric_enum_reverse_mapping_4509.txt @@ -0,0 +1 @@ +numeric enum reverse mapping ok diff --git a/test-parity/expected/test_parity_regex_replace_fn_lookahead.txt b/test-parity/expected/test_parity_regex_replace_fn_lookahead.txt new file mode 100644 index 0000000000..d86bac9de5 --- /dev/null +++ b/test-parity/expected/test_parity_regex_replace_fn_lookahead.txt @@ -0,0 +1 @@ +OK diff --git a/test-parity/expected/test_parity_request_subclass_stream_body.txt b/test-parity/expected/test_parity_request_subclass_stream_body.txt new file mode 100644 index 0000000000..464c5b1c98 --- /dev/null +++ b/test-parity/expected/test_parity_request_subclass_stream_body.txt @@ -0,0 +1,4 @@ +typeof r.text: function +typeof r[k]: function +literal text: HI +computed stream text: STREAMED diff --git a/test-parity/expected/test_parity_webassembly_graceful_fail_default.txt b/test-parity/expected/test_parity_webassembly_graceful_fail_default.txt new file mode 100644 index 0000000000..b758fc46b3 --- /dev/null +++ b/test-parity/expected/test_parity_webassembly_graceful_fail_default.txt @@ -0,0 +1,8 @@ +validate valid: false +compile rejected: true CompileError +mentions api: true +mentions issue: true +Module threw: true CompileError +Module mentions issue: true +loader result: degraded +program continues diff --git a/test-parity/known_failures.json b/test-parity/known_failures.json index 9cf9db1d7b..2a7160cf20 100644 --- a/test-parity/known_failures.json +++ b/test-parity/known_failures.json @@ -808,6 +808,12 @@ "linux" ] }, + "test_parity_destructuring_iterator_assignment": { + "issue": "4229", + "added": "2026-08-22", + "category": "bug-stale", + "reason": "Migration from the loose regression script exposed that object destructuring into an inherited setter has regressed after #4229; Perry exits at the setter-value assertion while Node prints ok. Remove this entry when the setter assignment is fixed." + }, "test_parity_dotenv": { "issue": "8271", "added": "2026-08-17", @@ -835,6 +841,12 @@ "linux" ] }, + "test_parity_method_value_snapshot_bind": { + "issue": "4548", + "added": "2026-08-22", + "category": "bug-stale", + "reason": "Migration from the loose regression script exposed a recurrence of #4548: a captured method invoked after the own property is reassigned loses its receiver and returns C:undefined. Remove this entry when method-value snapshots are fixed." + }, "test_parity_moment": { "issue": "8271", "added": "2026-08-17", diff --git a/test-parity/node-suite/crypto/create-key-invalid-throws.ts b/test-parity/node-suite/crypto/create-key-invalid-throws.ts new file mode 100644 index 0000000000..8c7ccfe410 --- /dev/null +++ b/test-parity/node-suite/crypto/create-key-invalid-throws.ts @@ -0,0 +1,10 @@ +import { createPrivateKey, createPublicKey, createSecretKey } from "node:crypto"; +let priv = false; +try { createPrivateKey("test-secret-key"); } catch { priv = true; } +if (!priv) throw new Error("createPrivateKey should throw on a non-PEM string"); +let pub = false; +try { createPublicKey("test-secret-key"); } catch { pub = true; } +if (!pub) throw new Error("createPublicKey should throw on a non-PEM string"); +const sk: any = createSecretKey(Buffer.from("test-secret-key")); +if (sk.type !== "secret") throw new Error("createSecretKey type: " + sk.type); +console.log("OK"); diff --git a/test-parity/node-suite/http/post-symbol-handle.ts b/test-parity/node-suite/http/post-symbol-handle.ts new file mode 100644 index 0000000000..589382e05f --- /dev/null +++ b/test-parity/node-suite/http/post-symbol-handle.ts @@ -0,0 +1,38 @@ +import * as http from "node:http"; +const wrapBodyStream = Symbol("wrapBodyStream"); +const server = http.createServer((req: any, res: any) => { + let symOk = false; + try { + req[wrapBodyStream] = true; + symOk = req[wrapBodyStream] === true; + } catch (e: any) { + res.statusCode = 500; + res.end("SYMBOL_THREW:" + e?.message); + return; + } + let body = ""; + req.on("data", (data: any) => { body += data; }); + req.on("end", () => { + res.statusCode = 200; + res.end("sym=" + symOk + ";body=" + body); + }); +}); +server.listen(0, () => { + const port = (server.address() as any).port; + const data = "hello-post-body"; + const request = http.request( + { host: "127.0.0.1", port, path: "/", method: "POST", headers: { "content-length": data.length } }, + (response: any) => { + let out = ""; + response.setEncoding("utf8"); + response.on("data", (chunk: any) => { out += chunk; }); + response.on("end", () => { + console.log("STATUS=" + response.statusCode); + console.log("RESP=" + out); + server.close(); + }); + }, + ); + request.write(data); + request.end(); +}); diff --git a/test-parity/node-suite/http2/server-request-value.ts b/test-parity/node-suite/http2/server-request-value.ts new file mode 100644 index 0000000000..12cb3dd723 --- /dev/null +++ b/test-parity/node-suite/http2/server-request-value.ts @@ -0,0 +1,7 @@ +import { Http2ServerRequest, Http2ServerResponse } from "node:http2"; +if (typeof Http2ServerRequest !== "function") throw new Error("Http2ServerRequest type"); +if (typeof Http2ServerResponse !== "function") throw new Error("Http2ServerResponse type"); +const someObj: any = { url: "/x" }; +if (someObj instanceof Http2ServerRequest) throw new Error("plain object matched request"); +if ((42 as any) instanceof Http2ServerResponse) throw new Error("number matched response"); +console.log("OK"); diff --git a/test-parity/node-suite/object/class-expr-capture-refresh-edge.js b/test-parity/node-suite/object/class-expr-capture-refresh-edge.ts similarity index 100% rename from test-parity/node-suite/object/class-expr-capture-refresh-edge.js rename to test-parity/node-suite/object/class-expr-capture-refresh-edge.ts diff --git a/test-parity/node-suite/process/env/value.ts b/test-parity/node-suite/process/env/value.ts new file mode 100644 index 0000000000..6361d9ffe7 --- /dev/null +++ b/test-parity/node-suite/process/env/value.ts @@ -0,0 +1,7 @@ +const env: any = process.env; +if (typeof env !== "object" || env === null) throw new Error("typeof process.env: " + typeof env); +if (env.PATH !== process.env.PATH) throw new Error("whole-object member read diverged"); +if (Object.keys(env).length < 1) throw new Error("no keys"); +function count(o: Record): number { return Object.keys(o).length; } +if (count(process.env as any) < 1) throw new Error("passed-whole count 0"); +console.log("OK"); diff --git a/test-parity/node-suite/stream/prototype-eventemitter-borrow.ts b/test-parity/node-suite/stream/prototype-eventemitter-borrow.ts new file mode 100644 index 0000000000..bc0af69f4b --- /dev/null +++ b/test-parity/node-suite/stream/prototype-eventemitter-borrow.ts @@ -0,0 +1,27 @@ +import StreamModule from "node:stream"; +const Stream: any = StreamModule; +let failures = 0; +if (typeof Stream.prototype !== "object" || typeof Stream.prototype.on !== "function") { + failures++; +} else { + const a: any = {}; + let gotA: any = null; + Stream.prototype.on.call(a, "data", (x: any) => { gotA = x; }); + Stream.prototype.emit.call(a, "data", 11); + if (gotA !== 11) failures++; +} +const SS: any = Stream.Stream; +if (typeof SS.prototype.on !== "function" || typeof SS.prototype.removeListener !== "function") { + failures++; +} else { + const b: any = {}; + let count = 0; + const fn = () => { count++; }; + SS.prototype.on.call(b, "x", fn); + SS.prototype.emit.call(b, "x"); + SS.prototype.removeListener.call(b, "x", fn); + SS.prototype.emit.call(b, "x"); + if (count !== 1) failures++; +} +if (failures !== 0) throw new Error("Stream.prototype EventEmitter borrow regression failed"); +console.log("stream proto ee borrow ok"); diff --git a/tests/issue_3908_tty_write_stream_pipe.js b/test-parity/node-suite/tty/write-stream-pipe.ts similarity index 100% rename from tests/issue_3908_tty_write_stream_pipe.js rename to test-parity/node-suite/tty/write-stream-pipe.ts diff --git a/test-parity/node-suite/util/textencoder-hoisted-function-decl.ts b/test-parity/node-suite/util/textencoder-hoisted-function-decl.ts new file mode 100644 index 0000000000..e92552c345 --- /dev/null +++ b/test-parity/node-suite/util/textencoder-hoisted-function-decl.ts @@ -0,0 +1,15 @@ +import * as util from "node:util"; +function encodeLength(content: string) { + content = textEncoder.encode(content) as any; + return (content as any).byteLength; +} +var textEncoder = new util.TextEncoder(); +var nestedLen = (() => { + function encodeLengthNested(content: string) { + content = nestedTextEncoder.encode(content) as any; + return (content as any).byteLength; + } + var nestedTextEncoder = new util.TextEncoder(); + return encodeLengthNested("world"); +})(); +console.log("len=" + encodeLength("hello") + ",nested=" + nestedLen); diff --git a/test-parity/node_suite_baseline.json b/test-parity/node_suite_baseline.json index 00f9878ffd..ef2b9e7504 100644 --- a/test-parity/node_suite_baseline.json +++ b/test-parity/node_suite_baseline.json @@ -2,7 +2,7 @@ "_schema": { "description": "Floor baseline for scripts/node_suite_regression_check.py. Each module's run must produce pass >= floor.pass and total >= floor.total; dropping below either is a regression (exit 1). Improvements are always accepted and reported as ratchet candidates. Captured in the node-26 environment with scripts/node_suite_run.py (pre-warm + fast/slow lanes).", "oracle": "node v26.5.0 (pinned by .node-version)", - "note": "Full pass sets the floor for deterministic modules. Timing/racy modules (net, stream, diagnostics_channel, fs-promises) carry a small margin below observed pass so ordinary flake does not false-alarm; the guard still catches real regressions, which are large (e.g. dns 6->0, http 19->9). node_suite_run.normalize() scrubs environment-variant tokens (console.time hrtime durations, stack-trace frame lines) symmetrically before the stdout compare, so console uses a full floor (119) for its deterministic content. Three clean focused Perry runs against Node 26.5.0 set the expanded HTTP floor at a stable 22/52 and the expanded net floor at 16/47, with no crashes or timeouts. The granular http2 and https suites are stable at 32/59 and 6/47 respectively, with no margin. The deterministic readline suite is stable at 20/95 with no margin. A clean node-26 run at 2810/2863 (98.1%) refreshed the floors, then an independent Node 26.5.0 run set the deterministic child_process floor at 43/53." + "note": "Full pass sets the floor for deterministic modules. Timing/racy modules (net, stream, diagnostics_channel, fs-promises) carry a small margin below observed pass so ordinary flake does not false-alarm; the guard still catches real regressions, which are large (e.g. dns 6->0, http 19->9). node_suite_run.normalize() scrubs environment-variant tokens (console.time hrtime durations, stack-trace frame lines) symmetrically before the stdout compare, so console uses a full floor (119) for its deterministic content. Three clean focused Perry runs against Node 26.5.0 set the expanded HTTP matched floor at 22; #5891 adds one current unmatched regression and raises the total floor to 53. The expanded net floor is stable at 16/47. The granular http2 matched floor is 32; #5891 adds one current unmatched regression and raises its total floor to 60. The https suite is stable at 6/47 with no margin. The deterministic readline suite is stable at 20/95 with no margin. A clean node-26 run at 2810/2863 (98.1%) refreshed the floors, then an independent Node 26.5.0 run set the deterministic child_process floor at 43/53." }, "modules": { "assert": { @@ -38,8 +38,8 @@ "total": 4 }, "crypto": { - "pass": 240, - "total": 242 + "pass": 241, + "total": 243 }, "dgram": { "pass": 42, @@ -85,13 +85,13 @@ }, "http": { "pass": 22, - "total": 52 + "total": 53 }, "http2": { "pass": 32, - "total": 59, + "total": 60, "outcomes": { - "diff": 27, + "diff": 28, "node_err": 0, "compile_fail": 0, "perry_err": 0 @@ -134,8 +134,8 @@ "total": 2 }, "object": { - "pass": 23, - "total": 23 + "pass": 24, + "total": 24 }, "os": { "pass": 39, @@ -150,8 +150,8 @@ "total": 148 }, "process": { - "pass": 95, - "total": 96 + "pass": 96, + "total": 97 }, "punycode": { "pass": 8, @@ -184,8 +184,8 @@ "total": 40 }, "stream": { - "pass": 770, - "total": 801 + "pass": 771, + "total": 802 }, "string": { "pass": 2, @@ -216,16 +216,16 @@ "total": 24 }, "tty": { - "pass": 32, - "total": 32 + "pass": 33, + "total": 33 }, "url": { "pass": 67, "total": 67 }, "util": { - "pass": 86, - "total": 86 + "pass": 87, + "total": 87 }, "v8": { "pass": 14, diff --git a/tests/test_684_type_only_namespace_collision.sh b/tests/test_684_type_only_namespace_collision.sh deleted file mode 100755 index b6332ef23a..0000000000 --- a/tests/test_684_type_only_namespace_collision.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -# Regression for #684: type-only namespace imports must not register their -# source module's exports into `import_function_prefixes` (or -# `namespace_member_prefixes`). -# -# Bug: `import type * as Schema from "./Schema.js"` was iterated like a -# value namespace import in compile.rs's resolution loop, so Schema's -# exports were registered into the flat name→source-prefix map. When the -# same module also had a real `import { TaggedError } from "./Data.js"` -# AND Schema.ts also exported `TaggedError`, HashMap iteration order -# determined the winner — and when Schema's entry won, top-level -# `TaggedError(...)` dispatched into Schema's TaggedError instead of -# Data's. Worse, Schema is type-only so it isn't in `module_init_deps` -# either, meaning its backing global is still 0.0 — the call threw -# `TypeError: value is not a function` from `js_closure_call1`. -# -# Fix: skip whole-decl type-only imports in compile.rs's resolution loop -# (mirroring the L3234 `module_init_deps` filter from #680). - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -# Data.ts: the REAL `kindOf` that the importer wants. -cat > "$TMPDIR/Data.ts" << 'EOF' -export const kindOf = (x: unknown): string => "data:" + typeof x; -EOF - -# Schema.ts: a DIFFERENT `kindOf` that exists only so the type-only -# import can collide. Importantly, Schema.ts is NOT initialized at -# runtime (no value-position reference), so `Schema_ts__init` never -# runs and the backing global stays uninitialized. Pre-fix, the -# importer would call _into_ this uninitialized symbol and throw -# `TypeError: value is not a function`. -cat > "$TMPDIR/Schema.ts" << 'EOF' -export const kindOf = (_x: unknown): string => { - throw new Error("Schema.kindOf must never be reached at runtime"); -}; -EOF - -# main.ts: imports `kindOf` as a value FROM Data, AND imports Schema -# as a TYPE-ONLY namespace. The type-only line should be erased at -# runtime — `kindOf(42)` must dispatch to Data's kindOf and return -# the expected `"data:number"`. Pre-fix, Schema.ts's type-only import -# leaked into the name→prefix map and (depending on HashMap order) -# the call landed on Schema_ts's uninitialized global slot. -cat > "$TMPDIR/main.ts" << 'EOF' -import { kindOf } from "./Data"; -import type * as Schema from "./Schema"; - -console.log(kindOf(42)); -console.log(kindOf("hi")); - -// Reference the type-only binding so TS keeps the import, but only -// in type position — the resulting JS is a noop. -type _Unused = ReturnType; -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="data:number -data:string" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: #684 type-only namespace collision still leaks" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_chained_cross_module_getter.sh b/tests/test_chained_cross_module_getter.sh deleted file mode 100755 index 1ebc1b2c76..0000000000 --- a/tests/test_chained_cross_module_getter.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash -# Regression: chained cross-module getter access returned `undefined`. -# -# Bug: `ImportedClass.field_names` carried only names, not types, so the -# stub class registered into `class_table` had every field typed -# `Type::Any`. `receiver_class_name`'s `PropertyGet` recursion at -# `type_analysis.rs:1030` walks `class.fields[i].ty` to find the receiver -# class for the next hop in a chain — `Type::Any` collapses that walk. -# Compounding the issue, only directly-imported classes ended up in -# `imported_classes`; transitively-referenced ones (`vm.viewport.scroll` -# where Viewport and Scroll are imported by the EditorViewModel module -# but not by the importer) weren't in `class_table` at all. -# -# Fix: -# - `ImportedClass.field_types: Vec` populated from the source class. -# - Fixed-point closure in compile.rs pulls in transitively-referenced -# classes whose names appear in `field_types` and exist in -# `exported_classes`. -# -# This test exercises a 3-deep chain (vm → viewport → scroll → getter) -# split across the importer + imported library so the closure has work -# to do (Viewport and Scroll are not in the importer's direct import list). - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/lib.ts" << 'EOF' -export class Scroll { - private _top: number = 42; - get scrollTop(): number { return this._top; } -} -export class Viewport { - readonly scroll: Scroll; - constructor() { this.scroll = new Scroll(); } -} -export class VM { - readonly viewport: Viewport; - constructor() { this.viewport = new Viewport(); } -} -EOF - -cat > "$TMPDIR/main.ts" << 'EOF' -import { VM } from './lib'; -const vm = new VM(); -// Chain through three modules (well, one library file but three classes) -// where only `VM` is directly named in the importer. -console.log("chain=" + vm.viewport.scroll.scrollTop); -// Same chain via local — exercises the type-flow path (the local must -// be inferred as Scroll for the getter dispatch to find it). -const s = vm.viewport.scroll; -console.log("local=" + s.scrollTop); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="chain=42 -local=42" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: chained cross-module getter returned wrong value" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_class_expr_capture_refresh_6604.sh b/tests/test_class_expr_capture_refresh_6604.sh deleted file mode 100755 index c4591c86c8..0000000000 --- a/tests/test_class_expr_capture_refresh_6604.sh +++ /dev/null @@ -1,173 +0,0 @@ -#!/bin/bash -# Regression (#6604): a class EXPRESSION capturing an enclosing-function var -# assigned AFTER the class in source order — semver's shape in every bundled -# class file: -# -# var Comparator = class _Comparator { -# constructor(comp, options) { options = parseOptions(options); ... } -# }; -# module.exports = Comparator; -# var parseOptions = require_parse_options(); // assigned AFTER the class -# -# Correct JS: the ctor closes over the live binding; by the time anything -# constructs a Comparator the wrapper has completed and the binding holds the -# function. Pre-fix, the #6037/#6052 end-of-body capture-refresh machinery -# scanned only `ast::Decl::Class` DECLARATION statements, so no refresh was -# ever emitted for the expression shape: DYNAMIC construction of the escaped -# class value replayed the stale declaration-time snapshot (captured var = -# undefined forever) and threw "TypeError: value is not a function" at -# pi-native init (wall #2 of the pi coding-agent bring-up, behind #6593). -# -# Covers: var/let/const-assigned named and anonymous class expressions, -# argument-position class expressions, the esbuild `__commonJS` semver layout, -# captured-var reassignment tracking, and multi-evaluation factory isolation -# for the normal (assigned-before-class) shape. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi -if ! command -v cc >/dev/null 2>&1; then - echo "SKIP: cc not available" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.js" << 'EOF' -// Shape 1: var-assigned NAMED class expression, captured var assigned after. -var wrapVar = function () { - var C = class _C { - constructor(x) { this.v = helper(x); } - }; - var out = { K: C }; - var helper = function (s) { return "var:" + s; }; - return out; -}; -console.log(new (wrapVar().K)("a").v); - -// Shape 2: let / const anonymous class expressions. -var wrapLet = function () { - let C = class { - constructor(x) { this.v = helperL(x); } - }; - var box = { K: C }; - var helperL = function (s) { return "let:" + s; }; - return box; -}; -var wrapConst = function () { - const C = class { - constructor(x) { this.v = helperC(x); } - }; - var box = { K: C }; - var helperC = function (s) { return "const:" + s; }; - return box; -}; -console.log(new (wrapLet().K)("b").v); -console.log(new (wrapConst().K)("c").v); - -// Shape 3: class expression in ARGUMENT position (no binding statement). -var registry = {}; -var register = function (name, cls) { registry[name] = cls; }; -var wrapArg = function () { - register("K", class { - constructor(x) { this.v = helperA(x); } - }); - var helperA = function (s) { return "arg:" + s; }; -}; -wrapArg(); -console.log(new registry.K("d").v); - -// Shape 4: the esbuild __commonJS semver comparator.js layout. -var __commonJS = (cb, mod) => function __require() { - return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; -}; -var require_parse_options = __commonJS({ - "parse-options.js"(exports, module) { - "use strict"; - var emptyOpts = Object.freeze({}); - module.exports = (options) => - options && typeof options === "object" ? options : emptyOpts; - }, -}); -var require_comparator = __commonJS({ - "comparator.js"(exports, module) { - "use strict"; - var ANY = Symbol("SemVer ANY"); - var Comparator = class _Comparator { - static get ANY() { return ANY; } - constructor(comp, options) { - options = parseOptions(options); - if (comp instanceof _Comparator) { comp = comp.value; } - this.loose = !!options.loose; - this.value = String(comp); - } - toString() { return this.value; } - }; - module.exports = Comparator; - var parseOptions = require_parse_options(); - }, -}); -var Comparator = require_comparator(); -var minimum = [new Comparator(">=0.0.0-0")]; -var c2 = new Comparator(">=1.2.3", { loose: true }); -console.log(String(minimum[0]), c2.value, c2.loose); - -// Refresh must track REASSIGNMENT of a captured var, not just its init. -var wrapReassign = function () { - var C = class { - constructor() { this.v = tag(); } - }; - var out = { K: C }; - var tag = function () { return "first"; }; - tag = function () { return "second"; }; - return out; -}; -console.log(new (wrapReassign().K)().v); - -// Per-evaluation isolation of the normal (assigned-before-class) shape. -var mk = function (t) { - var prefix = "p" + t; - var C = class { - constructor() { this.v = t + ":" + prefix; } - }; - return C; -}; -var A = mk("x"); -var B = mk("y"); -console.log(new A().v, new B().v); -EOF - -cd "$TMPDIR" -COMPILE_OUTPUT=$(PERRY_NO_AUTO_OPTIMIZE=1 "$PERRY" compile main.js -o test_bin --no-cache 2>&1) || { - echo "FAIL: compile error" - echo "$COMPILE_OUTPUT" | tail -20 - exit 1 -} - -RUN_OUTPUT=$(./test_bin 2>&1) -EXPECTED="var:a -let:b -const:c -arg:d ->=0.0.0-0 >=1.2.3 true -second -x:px y:py" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: class-expression capture snapshot stale (vars assigned after the class)" -echo "Expected:" -echo "$EXPECTED" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_class_expr_capture_refresh_6654.sh b/tests/test_class_expr_capture_refresh_6654.sh deleted file mode 100755 index a88d457c15..0000000000 --- a/tests/test_class_expr_capture_refresh_6654.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# Regression (#6654): capture refreshes for class expressions must belong to -# the evaluated heap class object, including parameter defaults and module -# blocks. A template-name-keyed snapshot lets a later factory call overwrite an -# earlier class's environment; dropping/stranding refresh entries leaves later -# parameter/body assignments invisible. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT="$SCRIPT_DIR/.." -FIXTURE="$ROOT/test-parity/node-suite/object/class-expr-capture-refresh-edge.js" -PERRY="${PERRY:-$ROOT/target/release/perry}" -[ ! -f "$PERRY" ] && PERRY="$ROOT/target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi -if ! command -v node >/dev/null 2>&1; then - echo "SKIP: node not available" - exit 0 -fi -if ! command -v cc >/dev/null 2>&1; then - echo "SKIP: cc not available" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap 'rm -rf "$TMPDIR"' EXIT - -NODE_OUTPUT=$(node "$FIXTURE") -COMPILE_OUTPUT=$( - PERRY_NO_AUTO_OPTIMIZE=1 "$PERRY" compile "$FIXTURE" \ - -o "$TMPDIR/test_bin" --no-cache 2>&1 -) || { - echo "FAIL: compile error" - echo "$COMPILE_OUTPUT" | tail -20 - exit 1 -} -PERRY_OUTPUT=$("$TMPDIR/test_bin") - -if [ "$PERRY_OUTPUT" = "$NODE_OUTPUT" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: class-expression capture refresh diverged from Node" -echo "Node:" -echo "$NODE_OUTPUT" -echo "Perry:" -echo "$PERRY_OUTPUT" -exit 1 diff --git a/tests/test_cross_module_getter.sh b/tests/test_cross_module_getter.sh deleted file mode 100755 index a63e55dd01..0000000000 --- a/tests/test_cross_module_getter.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -# Regression: cross-module class getters returned `undefined`. -# -# Bug history: -# - `ImportedClass` carried `method_names` but no `getter_names`, so the -# dispatch site at `expr.rs::PropertyGet` never registered a -# `(class, "__get_")` entry for imported classes. Reading -# `obj.prop` for an imported getter fell through to a runtime helper -# that doesn't know about cross-module accessor descriptors and -# silently returned `undefined`. -# - Fixed by adding `getter_names`/`setter_names` to `ImportedClass` and -# a registration loop in the Phase F method-symbol pass. -# -# This test covers the single-hop case: `import { Box } from './lib'; -# const b = new Box(); console.log(b.prop)`. The chained case is covered -# by `test_chained_cross_module_getter.sh`. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/lib.ts" << 'EOF' -export class Box { - field: number = 100; - method(): number { return 200; } - get prop(): number { return 300; } -} -EOF - -cat > "$TMPDIR/main.ts" << 'EOF' -import { Box } from './lib'; -const b = new Box(); -console.log("field=" + b.field); -console.log("method=" + b.method()); -console.log("prop=" + b.prop); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="field=100 -method=200 -prop=300" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: cross-module getter returned wrong value" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_crypto_create_key_invalid_throws.sh b/tests/test_crypto_create_key_invalid_throws.sh deleted file mode 100755 index 6033ace66c..0000000000 --- a/tests/test_crypto_create_key_invalid_throws.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Node's `crypto.createPrivateKey` / `createPublicKey` THROW on input that is -# not valid key material (e.g. a plain non-PEM string), rather than producing a -# KeyObject with `type === undefined`. jsonwebtoken's sign()/verify() rely on -# this: they `try { createPrivateKey(secret) } catch { createSecretKey(...) }`, -# so a string HMAC secret must make createPrivateKey/createPublicKey throw to -# reach the createSecretKey fallback. Pre-fix perry returned the string as a -# bogus key (type undefined), so HS256 signing reported "secretOrPrivateKey -# must be a symmetric key" and verify reported "invalid algorithm". - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -import { createPrivateKey, createPublicKey, createSecretKey } from "crypto"; - -let priv = false; -try { createPrivateKey("test-secret-key"); } catch (_) { priv = true; } -if (!priv) throw new Error("createPrivateKey should throw on a non-PEM string"); - -let pub = false; -try { createPublicKey("test-secret-key"); } catch (_) { pub = true; } -if (!pub) throw new Error("createPublicKey should throw on a non-PEM string"); - -// The legitimate symmetric-key fallback still works and is type 'secret'. -const sk: any = createSecretKey(Buffer.from("test-secret-key")); -if (sk.type !== "secret") throw new Error("createSecretKey type: " + sk.type); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: createPrivateKey/createPublicKey throw on invalid key material" diff --git a/tests/test_ctor_return_override_standalone.sh b/tests/test_ctor_return_override_standalone.sh deleted file mode 100755 index d34944a072..0000000000 --- a/tests/test_ctor_return_override_standalone.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ECMAScript constructor return-override: a constructor that explicitly returns -# an object (or function) makes `new C()` evaluate to that object, not the -# freshly-allocated `this`. The default codegen path calls a shared standalone -# `_constructor` symbol (opt out with PERRY_INLINE_CTOR=1); that path -# discarded the ctor's return value and always yielded the empty default -# instance. chalk's `class Chalk { constructor(o){ return chalkFactory(o); } }` -# (no-constructor-return) depends on the function being returned. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -// Constructor returns a plain object. -class W1 { constructor() { return { kind: "obj" }; } } -const a: any = new W1(); -if (typeof a !== "object" || a.kind !== "obj") throw new Error("W1: " + typeof a + " " + a.kind); - -// Constructor returns a function (chalk's no-constructor-return shape). -function makeFn() { const f: any = (x: string) => "F:" + x; f.tag = "T"; return f; } -class W2 { constructor() { return makeFn(); } } -const b: any = new W2(); -if (typeof b !== "function" || b.tag !== "T" || b("x") !== "F:x") throw new Error("W2: " + typeof b); - -// Ordinary constructor (implicit return this) is unaffected. -class P { x: number; constructor(v: number) { this.x = v; } } -const p = new P(5); -if (p.x !== 5) throw new Error("P: " + p.x); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: constructor return-override honored on the standalone-symbol path" diff --git a/tests/test_defineproperty_prototype_getter_this.sh b/tests/test_defineproperty_prototype_getter_this.sh deleted file mode 100755 index f353ec94bf..0000000000 --- a/tests/test_defineproperty_prototype_getter_this.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env bash -# Regression: a getter installed via `Object.defineProperty(Class.prototype, -# name, { get })` must run with `this` bound to the INSTANCE, not the prototype -# object it lives on. -# -# Such a getter is an ordinary method closure whose body reads `this` from its -# captured receiver slot (not IMPLICIT_THIS). The inherited-accessor walk used -# to merely set IMPLICIT_THIS and call the closure, so the getter observed the -# prototype — `this.` came back undefined. winston's -# `Object.defineProperty(Logger.prototype, 'transports', { get() { const { -# pipes } = this._readableState; … } })` (read as `this.transports` inside the -# Logger constructor) then threw "Cannot convert undefined or null to object". -# -# Fix: the inherited prototype-accessor path routes through -# `invoke_accessor_getter`, which clones the getter closure with `this` rebound -# to the real receiver (matching the own-accessor read path). -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -SRC="$TMPDIR/defineproperty_proto_getter.ts" -BIN="$TMPDIR/defineproperty_proto_getter" - -cat >"$SRC" <<'TS' -let failures = 0; - -// Plain class: getter reads an own data field through `this`. -class A { - _x = 42; -} -Object.defineProperty(A.prototype, "px", { - get() { return (this as any)._x; }, -}); -const a: any = new A(); -if (a.px !== 42) { - console.log("FAIL: defineProperty prototype getter read prototype, not instance (px=" + a.px + ")"); - failures = failures + 1; -} - -// Getter that DESTRUCTURES a nested own object — the exact winston shape. -class L { - _readableState: any; - constructor() { this._readableState = { pipes: null }; } -} -Object.defineProperty(L.prototype, "transports", { - configurable: false, - enumerable: true, - get() { - const { pipes } = (this as any)._readableState; - return !Array.isArray(pipes) ? [pipes].filter(Boolean) : pipes; - }, -}); -const l: any = new L(); -let threw = false; -let result: any = "unset"; -try { - result = l.transports; -} catch (e) { - threw = true; -} -if (threw) { - console.log("FAIL: destructuring getter threw (this bound to prototype, _readableState undefined)"); - failures = failures + 1; -} else if (JSON.stringify(result) !== "[]") { - console.log("FAIL: destructuring getter wrong result: " + JSON.stringify(result)); - failures = failures + 1; -} - -if (failures !== 0) { - throw new Error("defineProperty prototype getter this-binding regression failed"); -} -console.log("defineProperty proto getter this ok"); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$SRC" -o "$BIN" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$BIN" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "defineProperty proto getter this ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: defineProperty prototype getter this-binding" diff --git a/tests/test_descriptor_decode_parity.sh b/tests/test_descriptor_decode_parity.sh deleted file mode 100755 index be06ffb06a..0000000000 --- a/tests/test_descriptor_decode_parity.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -# Regression for the single-pass descriptor decode (#6748 follow-up): the -# fast decode must be indistinguishable from the per-field spec path. Pins -# the exact conditions under which it must FALL BACK: descriptors with -# inherited fields (custom [[Prototype]]), accessor-backed fields (literal -# getters), class instances as descriptors, and Object.prototype pollution. -# Differential: perry output must byte-match node. -set -e -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -[ ! -f "$PERRY" ] && { echo "SKIP: perry binary not found"; exit 0; } -command -v node >/dev/null 2>&1 || { echo "SKIP: node not found"; exit 0; } -TMPDIR=$(mktemp -d); trap "rm -rf $TMPDIR" EXIT -COMPILE_ENV=() -{ [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; } && COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -cat > "$TMPDIR/main.ts" << 'EOF' -const show = (o: any, k: string) => { - const d = Object.getOwnPropertyDescriptor(o, k)!; - console.log(k, d.value, typeof d.get, d.writable, d.enumerable, d.configurable); -}; -// 1. plain literal descriptors (the fast-decoded shape) -const a: any = {}; -Object.defineProperty(a, "p1", { value: 1 }); -Object.defineProperty(a, "p2", { value: 2, writable: true, enumerable: true, configurable: true }); -Object.defineProperty(a, "p3", { enumerable: true, get: () => 3 }); -show(a, "p1"); show(a, "p2"); show(a, "p3"); console.log("p3-read", a.p3); -// 2. INHERITED descriptor fields (custom proto) must be honored -const dproto = { value: 42, enumerable: true }; -const dchild = Object.create(dproto); -Object.defineProperty(a, "inh", dchild); -show(a, "inh"); -// 3. ACCESSOR-BACKED descriptor fields must fire -let fired = 0; -const dacc = { get value() { fired++; return 7; }, enumerable: true }; -Object.defineProperty(a, "acc", dacc); -show(a, "acc"); console.log("getter-fired", fired >= 1); -// 4. class INSTANCE as descriptor with prototype getter named `value` -class DescLike { get value() { return 9; } } -Object.defineProperty(a, "cls", new DescLike()); -show(a, "cls"); -// 5. Object.prototype pollution honored (then cleaned) -(Object.prototype as any).enumerable = true; -Object.defineProperty(a, "pol", { value: 5 }); -show(a, "pol"); -delete (Object.prototype as any).enumerable; -Object.defineProperty(a, "unpol", { value: 6 }); -show(a, "unpol"); -// 6. attrs retention + accessor->data + data->accessor transitions -const b: any = {}; -Object.defineProperty(b, "t", { value: 1, writable: true, enumerable: true, configurable: true }); -Object.defineProperty(b, "t", { get: () => 2, configurable: true }); -show(b, "t"); console.log("t-read", b.t); -Object.defineProperty(b, "t", { value: 3 }); -show(b, "t"); -// 7. freeze/seal still enforced through the decode -const c: any = { x: 1 }; Object.freeze(c); -let threw = false; try { Object.defineProperty(c, "x", { value: 2 }); } catch { threw = true; } -console.log("frozen-throws", threw, c.x); -EOF -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -P=$(./test_bin 2>&1); N=$(node main.ts 2>&1) -if [ "$P" = "$N" ]; then echo "PASS"; exit 0; fi -echo "FAIL"; echo "--- node ---"; echo "$N"; echo "--- perry ---"; echo "$P"; diff <(echo "$N") <(echo "$P") || true; exit 1 diff --git a/tests/test_effect_number_isfinite_property_path.sh b/tests/test_effect_number_isfinite_property_path.sh deleted file mode 100755 index f26caa6687..0000000000 --- a/tests/test_effect_number_isfinite_property_path.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/effect_number_isfinite_property_path.ts" <<'TS' -let failures = 0; - -function check(label: string, actual: any, expected: any): void { - if (actual !== expected) { - console.log(label + ": expected " + String(expected) + ", got " + String(actual)); - failures = failures + 1; - } -} - -check("direct finite", Number.isFinite(1), true); -check("direct infinity", Number.isFinite(Infinity), false); -check("direct string", Number.isFinite("1"), false); - -check("global finite", globalThis.Number.isFinite(1), true); -check("global infinity", globalThis.Number.isFinite(Infinity), false); -check("global string", globalThis.Number.isFinite("1"), false); - -if (failures !== 0) { - throw new Error("Number.isFinite property-path parity failed"); -} - -console.log("effect Number.isFinite property path ok"); -TS - -"$PERRY" compile --no-auto-optimize "$TMPDIR/effect_number_isfinite_property_path.ts" \ - -o "$TMPDIR/effect_number_isfinite_property_path" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 - } - -"$TMPDIR/effect_number_isfinite_property_path" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "effect Number.isFinite property path ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: effect Number.isFinite property path" diff --git a/tests/test_export_const_static_field_alias.sh b/tests/test_export_const_static_field_alias.sh deleted file mode 100755 index 399bdadb55..0000000000 --- a/tests/test_export_const_static_field_alias.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A module-level const that aliases a class STATIC field value, re-exported via a -# separate `export { x }` clause, must be readable + callable cross-module. -# Before the fix its init lowered to `Expr::StaticFieldGet`, which was missing -# from the separate-clause export's `is_exportable` set — so the const never got -# a backing module global and the importer link-resolved a nonexistent -# `perry_fn___` symbol, making the call return `undefined`. -# zod's `z.string`/`z.number`/… are exactly this shape: -# const stringType = ZodString.create; export { stringType as string } - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/types.ts" <<'TS' -export class ZS { - v = 5; - static create = (): ZS => { return new ZS(); }; -} -const stringType = ZS.create; // const aliasing a static arrow field -export { stringType as string }; // separate-clause re-export with alias -TS -cat >"$TMPDIR/barrel.ts" <<'TS' -import * as z from "./types.js"; -export { z }; -TS -cat >"$TMPDIR/main.ts" <<'TS' -import { z } from "./barrel.js"; -import { string as direct } from "./types.js"; -const a = (z as any).string(); -if (!a || (a as any).v !== 5) throw new Error("via namespace: " + JSON.stringify(a)); -const b = (direct as any)(); -if (!b || (b as any).v !== 5) throw new Error("direct import: " + JSON.stringify(b)); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: export const static-field alias" diff --git a/tests/test_fetch_binary_body_round_trip.sh b/tests/test_fetch_binary_body_round_trip.sh deleted file mode 100755 index 140991e5df..0000000000 --- a/tests/test_fetch_binary_body_round_trip.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -# Regression: a fetch/Blob/Response body that is NOT valid UTF-8 (e.g. a -# fetched PNG/protobuf) must survive `arrayBuffer()` byte-exact, and a -# UTF-8 body must still decode losslessly through `text()`/`json()`. -# -# Bug: `js_response_array_buffer` / `js_blob_array_buffer` round-tripped the -# body through `from_utf8_unchecked` → String → a STRING_TAG JsValue. That -# was UB on non-UTF-8 bytes, and the JS `new Uint8Array(value)` dispatch keys -# on the Buffer POINTER_TAG — a STRING_TAG value reads as an EMPTY buffer. So -# `new Uint8Array(await res.arrayBuffer())` came back empty / corrupted for -# any binary payload. -# -# Fix: resolve a real Buffer (`alloc_buffer` + POINTER_TAG), byte-exact — -# the same shape `bytes()` already used. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -async function main() { - // Non-UTF-8 bytes: 0xFF 0xFE are invalid UTF-8, 0x00 is an embedded NUL. - const bin = new Uint8Array([0xFF, 0xFE, 0x00, 0x80, 0x50, 0x4E, 0x47]); - - // Blob.arrayBuffer() — binary must round-trip byte-exact. - const ab = await new Blob([bin]).arrayBuffer(); - console.log("blob.arrayBuffer:", Array.from(new Uint8Array(ab)).join(",")); - - // Response.text() — UTF-8 body (incl. a multi-byte char) preserved. - const res = new Response('{"msg":"héllo","n":42}'); - console.log("res.text:", await res.text()); - - // Response.json() — body parsed to an object. - const j = await new Response('{"msg":"héllo","n":42}').json(); - console.log("res.json:", (j as any).msg, (j as any).n); - - // Response.arrayBuffer() — bytes of a (UTF-8) body, byte-exact. - const ab2 = await new Response("ABC").arrayBuffer(); - console.log("res.arrayBuffer:", Array.from(new Uint8Array(ab2)).join(",")); -} -main().catch((e) => console.log("ERR:", e?.message ?? e)); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="blob.arrayBuffer: 255,254,0,128,80,78,71 -res.text: {\"msg\":\"héllo\",\"n\":42} -res.json: héllo 42 -res.arrayBuffer: 65,66,67" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: fetch/Blob binary body round-trip regressed" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_function_bind_value_reflect_apply.sh b/tests/test_function_bind_value_reflect_apply.sh deleted file mode 100755 index 86322fdad7..0000000000 --- a/tests/test_function_bind_value_reflect_apply.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `Function.prototype.bind` invoked as a *value* (not a `fn.bind(...)` method -# call) must create a bound function. It was installed as a no-op proto method, -# so `Reflect.apply(Function.prototype.bind, fn, [thisArg])` and -# `Function.prototype.bind.apply(fn, …)` returned `undefined`. The -# `Function.prototype.call.bind(method)` uncurry-this idiom in -# `call-bind-apply-helpers` (call-bound → side-channel → qs → Stripe) relies on -# `Reflect.apply(bind, call, [fn])` returning a real bound function. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -const bind = Function.prototype.bind; -const call = Function.prototype.call; - -// uncurry-this: `Function.prototype.call.bind(String.prototype.indexOf)` -// built via Reflect.apply(bind, call, [indexOf]). -const indexOf = String.prototype.indexOf; -const boundCall: any = (Reflect as any).apply(bind, call, [indexOf]); -if (typeof boundCall !== "function") throw new Error("not a function: " + typeof boundCall); -// boundCall("hello", "l") === "hello".indexOf("l") === 2 -if (boundCall("hello", "l") !== 2) throw new Error("uncurry: " + boundCall("hello", "l")); - -// Also via Function.prototype.bind.apply -function add(a: number, b: number) { return a + b; } -const g: any = (bind as any).apply(add, [null, 10]); // add.bind(null, 10) -if (typeof g !== "function") throw new Error("bind.apply not fn: " + typeof g); -if (g(5) !== 15) throw new Error("bind.apply partial: " + g(5)); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: Function.prototype.bind as a value (Reflect.apply / .apply)" diff --git a/tests/test_headers_handle_field_probe_segfault.sh b/tests/test_headers_handle_field_probe_segfault.sh deleted file mode 100755 index b421f5a248..0000000000 --- a/tests/test_headers_handle_field_probe_segfault.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# Regression (fix/object-field-read-segfault): js_object_get_own_field_or_undef -# must not dereference a Web-Fetch handle (Headers/Request/Response/Blob) as a -# heap object. -# -# A `new Headers()` is a NaN-boxed small handle id in [0x40000, 0x100000), not a -# heap pointer. When a user class declares a method whose name collides with a -# Headers method (`.set`/`.delete`/`.append`), the codegen dynamic-dispatch tower -# fires an own-property override probe (`js_object_get_own_field_or_undef`) on the -# receiver before dispatch. Pre-fix that probe floored pointer validation at -# 0x10000, so the 0x40000 Headers handle slipped through and the runtime -# dereferenced `[handle - GC_HEADER_SIZE]` as a GcHeader -> SIGSEGV. This crashed -# every Hono HTTP response (c.json -> #newResponse -> responseHeaders.set) on -# Linux x86_64. macOS arm64 masked it via is_valid_obj_ptr's higher heap floor. -# -# Expected: prints PASS and exits 0. Pre-fix: SIGSEGV (exit 139), no "PASS". -set -u -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SRC="$SCRIPT_DIR/test_headers_handle_field_probe_segfault.ts" -OUTPUT="/tmp/perry_test_headers_handle_field_probe_segfault" -PERRY="${PERRY:-perry}" - -if ! command -v "$PERRY" >/dev/null 2>&1 && [ ! -x "$PERRY" ]; then - echo "SKIP: perry binary not found (set PERRY=/path/to/perry)" - exit 0 -fi - -"$PERRY" compile "$SRC" -o "$OUTPUT" >/tmp/perry_test_hh_compile.log 2>&1 -if [ $? -ne 0 ]; then - echo "FAIL: compile error" - tail -20 /tmp/perry_test_hh_compile.log - exit 1 -fi - -RUN_OUTPUT="$("$OUTPUT" 2>&1)" -RUN_EXIT=$? -rm -f "$OUTPUT" - -if [ $RUN_EXIT -ne 0 ]; then - echo "FAIL: runtime exited $RUN_EXIT (139 = SIGSEGV = the regression)" - echo "$RUN_OUTPUT" - exit 1 -fi - -if echo "$RUN_OUTPUT" | grep -q "PASS"; then - echo "PASS" - exit 0 -else - echo "FAIL: unexpected output" - echo "$RUN_OUTPUT" - exit 1 -fi diff --git a/tests/test_headers_iterator_handle_segfault.sh b/tests/test_headers_iterator_handle_segfault.sh deleted file mode 100755 index 2c53bdd14b..0000000000 --- a/tests/test_headers_iterator_handle_segfault.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# Regression (fix/headers-iterator-segfault): js_get_iterator / -# is_builtin_iterator_class_id must not dereference a Web-Fetch handle -# (Headers / Request.headers / Response.headers) as a heap object. -# -# A `new Headers()` is a NaN-boxed small handle id in [0x40000, 0x100000), not -# a heap pointer. #4786 switched the `for...of` lowering for opaque iterables -# from the eager `js_for_of_to_array` path to the lazy iterator protocol -# (`GetIterator(obj)` -> `js_get_iterator`). `js_get_iterator` calls -# `is_builtin_iterator_class_id` to short-circuit values that are already -# iterators; pre-fix that helper floored pointer validation at 0x1008, so the -# 0x40000+ Headers handle slipped through and the runtime dereferenced -# `[handle - GC_HEADER_SIZE]` as a GcHeader -> SIGSEGV. This crashed every Hono -# HTTP response (`buildOutgoingHttpHeaders` iterates the response Headers) on -# Linux x86_64. macOS arm64 masked it via a higher heap floor. -# -# Expected: prints PASS and exits 0. Pre-fix: SIGSEGV (exit 139), no "PASS". -set -u -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SRC="$SCRIPT_DIR/test_headers_iterator_handle_segfault.ts" -OUTPUT="/tmp/perry_test_headers_iterator_handle_segfault" -PERRY="${PERRY:-perry}" - -if ! command -v "$PERRY" >/dev/null 2>&1 && [ ! -x "$PERRY" ]; then - echo "SKIP: perry binary not found (set PERRY=/path/to/perry)" - exit 0 -fi - -"$PERRY" compile "$SRC" -o "$OUTPUT" >/tmp/perry_test_hi_compile.log 2>&1 -if [ $? -ne 0 ]; then - echo "FAIL: compile error" - tail -20 /tmp/perry_test_hi_compile.log - exit 1 -fi - -RUN_OUTPUT="$("$OUTPUT" 2>&1)" -RUN_EXIT=$? -rm -f "$OUTPUT" - -if [ $RUN_EXIT -ne 0 ]; then - echo "FAIL: runtime exited $RUN_EXIT (139 = SIGSEGV = the regression)" - echo "$RUN_OUTPUT" - exit 1 -fi - -if echo "$RUN_OUTPUT" | grep -q "PASS"; then - echo "PASS" - exit 0 -else - echo "FAIL: unexpected output" - echo "$RUN_OUTPUT" - exit 1 -fi diff --git a/tests/test_http2_server_request_value.sh b/tests/test_http2_server_request_value.sh deleted file mode 100755 index d819b1c49e..0000000000 --- a/tests/test_http2_server_request_value.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `Http2ServerRequest` / `Http2ServerResponse` imported as VALUES from -# `node:http2` are used by libraries purely for `x instanceof Http2ServerRequest` -# brand checks (e.g. @hono/node-server distinguishing HTTP/2 from HTTP/1 -# requests). They resolved to `undefined`, so the `instanceof` RHS was not an -# object and threw "Right-hand side of 'instanceof' is not an object", 400'ing -# every request. They must be callable class values (so `instanceof` returns -# `false` for non-HTTP/2 values without throwing). - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -import { Http2ServerRequest, Http2ServerResponse } from "node:http2"; - -if (typeof Http2ServerRequest !== "function") { - throw new Error("Http2ServerRequest typeof: " + typeof Http2ServerRequest); -} -if (typeof Http2ServerResponse !== "function") { - throw new Error("Http2ServerResponse typeof: " + typeof Http2ServerResponse); -} -// instanceof must not throw and must be false for ordinary values. -const someObj: any = { url: "/x" }; -if (someObj instanceof Http2ServerRequest) throw new Error("plain obj matched Http2ServerRequest"); -if ((42 as any) instanceof Http2ServerResponse) throw new Error("number matched Http2ServerResponse"); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: node:http2 Http2ServerRequest/Response are callable for instanceof" diff --git a/tests/test_http_post_symbol_handle.sh b/tests/test_http_post_symbol_handle.sh deleted file mode 100755 index 2cfe03a58b..0000000000 --- a/tests/test_http_post_symbol_handle.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Regression: a SYMBOL-keyed property write on a native node:http -# `IncomingMessage` handle (e.g. @hono/node-server's -# `incoming[wrapBodyStream] = true` on the POST/PUT body path) used to throw -# `TypeError: Cannot assign to read only property 'property'` under strict mode, -# because `set_handle_property` reported the symbol write as a failed [[Set]]. -# That rejected write surfaced as Hono's `handleFetchError` returning a 500 for -# every POST/PUT (GET/HEAD have no body and skip the write), so the native HTTP -# server replied 500 to all body-carrying requests before the route ran. -# -# Fix: route symbol-keyed writes on a native handle into the per-object symbol -# side table (and read them back from it). This test drives a real node:http -# server with a POST body and asserts the symbol round-trips AND the body is -# delivered to the handler. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/srv.ts" <<'TS' -import * as http from "node:http"; - -const wrapBodyStream = Symbol("wrapBodyStream"); - -const server = http.createServer((req: any, res: any) => { - // The @hono/node-server adapter does exactly this on every non-GET/HEAD - // request. It must not throw, and must round-trip through the side table. - let symOk = false; - try { - req[wrapBodyStream] = true; - symOk = req[wrapBodyStream] === true; - } catch (e: any) { - res.statusCode = 500; - res.end("SYMBOL_THREW:" + (e && e.message)); - return; - } - - let body = ""; - req.on("data", (d: any) => { body += d; }); - req.on("end", () => { - res.statusCode = 200; - res.end("sym=" + symOk + ";body=" + body); - }); -}); - -server.listen(0, () => { - const port = (server.address() as any).port; - const data = "hello-post-body"; - const r = http.request( - { host: "127.0.0.1", port, path: "/", method: "POST", - headers: { "content-type": "text/plain", "content-length": data.length } }, - (resp: any) => { - let out = ""; - resp.setEncoding("utf8"); - resp.on("data", (c: any) => { out += c; }); - resp.on("end", () => { - console.log("STATUS=" + resp.statusCode); - console.log("RESP=" + out); - server.close(); - }); - }, - ); - r.write(data); - r.end(); -}); -TS - -# Run inside the tmpdir so `perry run` does not leave its compiled artifact -# (e.g. `srv`) in the caller's cwd / the repo tree. -OUT="$(cd "$TMPDIR" && "$PERRY" run "$TMPDIR/srv.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } - -if ! grep -q "^STATUS=200$" <<<"$OUT"; then - echo "FAIL: expected POST status 200, got:"; echo "$OUT"; exit 1; -fi -if ! grep -q "^RESP=sym=true;body=hello-post-body$" <<<"$OUT"; then - echo "FAIL: symbol round-trip or POST body wrong, got:"; echo "$OUT"; exit 1; -fi -echo "PASS: symbol-keyed write on a native IncomingMessage handle round-trips; POST body delivered" diff --git a/tests/test_imported_function_static_property.sh b/tests/test_imported_function_static_property.sh deleted file mode 100755 index b0c08d6ac4..0000000000 --- a/tests/test_imported_function_static_property.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reading a static property off a cross-module *imported function* must read the -# property — not invoke the function. PropertyGet on an `Expr::ExternFuncRef` -# unconditionally called the `perry_fn___()` getter (correct only for -# an exported const/let, whose symbol IS a value getter). For an imported -# *function*, that symbol is the function body, so every `Fn.staticProp` read -# invoked the function with zero args and read the property off its return value -# (undefined). Stripe hit this on `StripeResource.method` / `.extend` — an -# `export { StripeResource }` function carrying static props — so each static -# read constructed a StripeResource instead. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/res.ts" <<'TS' -function StripeResource(this: any, stripe: any, deprecated: any) { - if (deprecated) throw new Error("invoked with a 2nd arg: " + typeof deprecated); - this._stripe = stripe; -} -(StripeResource as any).method = function () { return "M"; }; -(StripeResource as any).extend = function () { return "E"; }; -(StripeResource as any).MAX = 42; -(StripeResource as any).prototype = { _stripe: null, path: "", initialize() {} }; -export { StripeResource }; -TS -cat >"$TMPDIR/main.ts" <<'TS' -import { StripeResource } from "./res.js"; -// Reads must NOT invoke the constructor. -if (typeof (StripeResource as any).method !== "function") throw new Error("method: " + typeof (StripeResource as any).method); -if (typeof (StripeResource as any).extend !== "function") throw new Error("extend: " + typeof (StripeResource as any).extend); -if ((StripeResource as any).MAX !== 42) throw new Error("MAX: " + (StripeResource as any).MAX); -// Built-in function props read off the imported function too. -if ((StripeResource as any).name !== "StripeResource") throw new Error("name: " + (StripeResource as any).name); -// Reading the static into a local then calling it (the Stripe pattern: -// `const stripeMethod = StripeResource.method;`) dispatches correctly. -const m: any = (StripeResource as any).method; -if (m() !== "M") throw new Error("method via local: " + m()); -const e: any = (StripeResource as any).extend; -if (e() !== "E") throw new Error("extend via local: " + e()); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: imported function static property read (no spurious invocation)" diff --git a/tests/test_imported_inherited_accessor_datatexture.sh b/tests/test_imported_inherited_accessor_datatexture.sh deleted file mode 100755 index cc86c35a15..0000000000 --- a/tests/test_imported_inherited_accessor_datatexture.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Regression for a Three.js/DataTexture-shaped native compile edge: -# constructor field inference in a subclass must not allocate an own data slot -# for `this.image = ...` when `image` is an inherited accessor from an imported -# superclass. Type-only `declare` refinements must not shadow inherited fields. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi -if [[ "$PERRY" != /* ]]; then - PERRY="$(cd "$(dirname "$PERRY")" && pwd)/$(basename "$PERRY")" -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/base_texture.ts" <<'TS' -export const LinearFilter = 1006; - -export class BaseTexture { - public source: any; - public version = 0; - public magFilter: number; - public minFilter: number; - public generateMipmaps = true; - public flipY = true; - public unpackAlignment = 4; - - constructor( - image: any = null, - mapping: any = undefined, - wrapS: any = undefined, - wrapT: any = undefined, - magFilter: number = LinearFilter, - minFilter: number = LinearFilter, - format: any = undefined, - type: any = undefined, - anisotropy: any = undefined, - colorSpace: any = undefined, - ) { - this.source = { data: image, version: 0 }; - this.magFilter = magFilter; - this.minFilter = minFilter; - } - - get image(): any { - return this.source.data; - } - - set image(value: any) { - this.source.data = value; - } - - get readOnlyTag(): string { - return "base-read-only"; - } - - set writeOnlyTag(value: string) { - this.source.writeOnlyTag = value; - } - - set needsUpdate(value: boolean) { - if (value === true) { - this.version++; - this.source.version++; - } - } -} - -export class AliasAccessorBase { - public marker: any = "unset"; - - get aliasedValue(): any { - return this.marker; - } - - set aliasedValue(value: any) { - this.marker = value; - } -} -TS - -cat >"$TMPDIR/main.ts" <<'TS' -import { AliasAccessorBase, BaseTexture } from "./base_texture"; - -const NearestFilter = 1003; - -function assert(condition: any, label: string): void { - if (!condition) throw new Error(label); -} - -class DataTextureLike extends BaseTexture { - declare magFilter: number; - declare minFilter: number; - declare generateMipmaps: boolean; - declare flipY: boolean; - declare unpackAlignment: number; - - public isDataTexture = true; - - constructor( - data: any = null, - width = 1, - height = 1, - format: any = undefined, - type: any = undefined, - mapping: any = undefined, - wrapS: any = undefined, - wrapT: any = undefined, - magFilter: number = NearestFilter, - minFilter: number = NearestFilter, - anisotropy: any = undefined, - colorSpace: any = undefined, - ) { - super(null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace); - this.image = { data, width, height }; - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - this.writeOnlyTag = "ctor-setter"; - } -} - -const payload = new Uint8Array(8); -const texture: any = new DataTextureLike(payload, 2, 1); - -assert(texture.isDataTexture === true, "subclass field initialized"); -assert(texture instanceof BaseTexture, "instanceof imported base"); -assert(texture.version === 0, "initial version"); -assert(texture.image !== undefined, "inherited getter returned image"); -assert(texture.image.data === payload, "payload identity preserved"); -assert(texture.image.width === 2, "width preserved"); -assert(texture.image.height === 1, "height preserved"); -assert(texture.readOnlyTag === "base-read-only", "getter-only inherited accessor read"); -assert(texture.source.writeOnlyTag === "ctor-setter", "setter-only inherited accessor constructor write"); -assert(texture.magFilter === NearestFilter, "magFilter subclass default"); -assert(texture.minFilter === NearestFilter, "minFilter subclass default"); -assert(texture.generateMipmaps === false, "subclass generateMipmaps override"); -assert(texture.flipY === false, "subclass flipY override"); -assert(texture.unpackAlignment === 1, "subclass unpackAlignment override"); -assert(!Object.keys(texture).includes("image"), "accessor write did not create own image slot"); -assert(!Object.keys(texture).includes("readOnlyTag"), "getter-only inherited accessor did not create own slot"); -assert(!Object.keys(texture).includes("writeOnlyTag"), "setter-only inherited accessor did not create own slot"); - -class AliasAccessorChild extends AliasAccessorBase {} - -const aliasProto: any = AliasAccessorChild.prototype; -aliasProto.aliasedValue = function badAliasPatch() { - return "bad"; -}; - -const aliasInstance: any = new AliasAccessorChild(); -aliasInstance.aliasedValue = "instance-setter"; -assert(aliasInstance.aliasedValue === "instance-setter", "prototype alias write preserved accessor dispatch"); -assert(!Object.keys(aliasInstance).includes("aliasedValue"), "prototype alias accessor write did not create own slot"); - -class StaticAccessorPollution { - static get constructorSlot(): string { - return "static-slot"; - } - - static set constructorSlot(_value: string) {} - - constructor() { - (this as any).constructorSlot = "instance-slot"; - } -} - -const staticPollution: any = new StaticAccessorPollution(); -assert(StaticAccessorPollution.constructorSlot === "static-slot", "static accessor still works"); -assert(staticPollution.constructorSlot === "instance-slot", "static accessor did not suppress instance field"); -assert(Object.keys(staticPollution).includes("constructorSlot"), "instance constructor assignment remained an own field"); - -const replacement = { data: payload, width: 4, height: 3 }; -texture.image = replacement; -assert(texture.image === replacement, "inherited setter assignment round-trips"); - -texture.writeOnlyTag = "post-constructor"; -assert(texture.source.writeOnlyTag === "post-constructor", "setter-only inherited accessor post-constructor write"); - -texture.needsUpdate = true; -assert(texture.version === 1, "needsUpdate increments texture version"); -assert(texture.source.version === 1, "needsUpdate increments source version"); - -console.log("OK"); -TS - -cd "$TMPDIR" -"$PERRY" compile --no-cache --no-auto-optimize main.ts --output test_bin >/dev/null -RUN_OUTPUT="$(./test_bin 2>&1)" - -if [[ "$RUN_OUTPUT" == "OK" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: imported inherited accessor DataTexture semantics regressed" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_inherited_ctor_arg_forwarding.sh b/tests/test_inherited_ctor_arg_forwarding.sh deleted file mode 100755 index 88f61409ec..0000000000 --- a/tests/test_inherited_ctor_arg_forwarding.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `new Sub(arg)` where Sub has NO own constructor but its parent does must forward -# `arg` to the parent ctor (JS implicit `constructor(...args){ super(...args) }`). -# The recursion-guarded "symbol-call" construction path (taken when `new Sub(...)` -# appears inside a method of Sub) computed the ctor arity from Sub's own ctor — -# `None` → 0 — so it dropped every argument; the synthesized ctor's forwarded -# params then read uninitialized and the inherited `this.x = arg` stored garbage. -# Pervasive in zod: `new ZodNumber({checks:[...]})` from `_addCheck` (ZodNumber has -# no own ctor, ZodType does) made `_def` undefined → `[...this._def.checks]` threw. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/c.ts" <<'TS' -abstract class Base { - _def: any; - constructor(def: any) { this._def = def; } - abstract _p(): any; -} -class Num extends Base { // no own constructor — inherits Base's - _p() { return 1; } - // `new Num(...)` issued INSIDE a Num method -> recursion-guarded symbol-call path - addCheck(c: any): Num { return new Num({ ...this._def, checks: [...this._def.checks, c] }); } - static create = (): Num => new Num({ checks: [], tn: "N" }); -} - -const a: any = Num.create(); -if (JSON.stringify(a._def) !== '{"checks":[],"tn":"N"}') throw new Error("create _def: " + JSON.stringify(a._def)); -const b: any = a.addCheck({ kind: "int" }); -if (JSON.stringify(b._def.checks) !== '[{"kind":"int"}]') throw new Error("addCheck _def: " + JSON.stringify(b._def)); -const c: any = a.addCheck({ kind: "int" }).addCheck({ kind: "min" }); -if (c._def.checks.length !== 2) throw new Error("chain: " + JSON.stringify(c._def.checks)); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/c.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: inherited-ctor argument forwarding" diff --git a/tests/test_inline_closure_capturing_local.sh b/tests/test_inline_closure_capturing_local.sh deleted file mode 100755 index 42e6920851..0000000000 --- a/tests/test_inline_closure_capturing_local.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$ROOT/target/release/perry}}" -RUNTIME_DIR="${PERRY_RUNTIME_DIR:-$ROOT/target/release}" -FIXTURE="$ROOT/tests/issue_inline_closure_capturing_local.js" -WORKDIR="${TMPDIR:-/tmp}/perry-inline-closure-capture-$$" -BIN="$WORKDIR/perry-inline-closure-capture" -COMPILE_LOG="$WORKDIR/compile.log" -OUT_LOG="$WORKDIR/out.log" - -mkdir -p "$WORKDIR" -trap 'rm -rf "$WORKDIR"' EXIT - -if [[ ! -x "$PERRY" ]]; then - PERRY="$ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build --release -p perry)" - exit 0 -fi - -env PERRY_ALLOW_UNIMPLEMENTED=1 PERRY_RUNTIME_DIR="$RUNTIME_DIR" "$PERRY" compile --no-cache --no-auto-optimize "$FIXTURE" -o "$BIN" \ - >"$COMPILE_LOG" 2>&1 || { - cat "$COMPILE_LOG" >&2 - exit 1 - } - -set +e -"$BIN" >"$OUT_LOG" 2>&1 -rc=$? -set -e - -if [[ "$rc" -ne 0 ]]; then - echo "inlined-closure-capture fixture failed (exit $rc):" >&2 - cat "$OUT_LOG" >&2 - exit 1 -fi - -grep -qx "inlined closure captures ok" "$OUT_LOG" || { - echo "unexpected output:" >&2 - cat "$OUT_LOG" >&2 - exit 1 -} diff --git a/tests/test_issue_3583_async_semantic_parity.sh b/tests/test_issue_3583_async_semantic_parity.sh deleted file mode 100644 index 4d20b8854e..0000000000 --- a/tests/test_issue_3583_async_semantic_parity.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# Regression coverage for #3583 / Test262 async-function semantic parity: -# direct async bodies must resolve/reject through Promises, default-param -# abrupt completions must reject, and `.length` must stop at the first default. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -COMPILE_ENV=() -if [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; then - COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -async function returnsValue() { - return 42; -} - -function failDefault(): number { - throw "default-boom"; -} - -function failArrowDefault(): number { - throw "arrow-default-boom"; -} - -async function rejectsDefault(value = failDefault()) { - console.log("default body ran"); - return value; -} - -async function throwsNow() { - throw "throw-boom"; -} - -async function lengthOne(a: number, b = 39,) { - return a + b; -} - -const arrowLengthOne = async (a: number, b = 39,) => { - return a + b; -}; - -const rejectsArrowDefault = async (value = failArrowDefault()) => { - console.log("arrow body ran"); - return value; -}; - -returnsValue().then((value) => console.log("return " + value)); -rejectsDefault().then( - () => console.log("default unexpectedly resolved"), - (err) => console.log("default rejected " + err) -); -throwsNow().then( - () => console.log("throw unexpectedly resolved"), - (err) => console.log("throw rejected " + err) -); -rejectsArrowDefault().then( - () => console.log("arrow default unexpectedly resolved"), - (err) => console.log("arrow default rejected " + err) -); - -console.log("fn length " + lengthOne.length); -console.log("arrow length " + arrowLengthOne.length); -console.log("sync end"); -EOF - -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="fn length 1 -arrow length 1 -sync end -return 42 -default rejected default-boom -throw rejected throw-boom -arrow default rejected arrow-default-boom" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: async semantic parity regression" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_3655_builtin_static_name_length.sh b/tests/test_issue_3655_builtin_static_name_length.sh deleted file mode 100755 index e06b390397..0000000000 --- a/tests/test_issue_3655_builtin_static_name_length.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/issue_3655.ts" <<'TS' -let failures = 0; - -function check(label: string, actual: any, expected: any): void { - if (actual !== expected) { - console.log(label + ": expected " + String(expected) + ", got " + String(actual)); - failures = failures + 1; - } -} - -function checkDataDescriptor(label: string, desc: any, value: any): void { - if (desc === undefined) { - console.log(label + ": descriptor missing"); - failures = failures + 1; - return; - } - check(label + ".value", desc.value, value); - check(label + ".writable", desc.writable, false); - check(label + ".enumerable", desc.enumerable, false); - check(label + ".configurable", desc.configurable, true); -} - -checkDataDescriptor("Array.from.name", Object.getOwnPropertyDescriptor(Array.from, "name"), "from"); -checkDataDescriptor("Array.from.length", Object.getOwnPropertyDescriptor(Array.from, "length"), 1); -check("BigInt.asIntN.length", BigInt.asIntN.length, 2); -check("Reflect.apply.name", Reflect.apply.name, "apply"); -check("Reflect.apply.length", Reflect.apply.length, 3); -checkDataDescriptor( - "Reflect.apply.length", - Object.getOwnPropertyDescriptor(Reflect.apply, "length"), - 3, -); -checkDataDescriptor("Math.max.length", Object.getOwnPropertyDescriptor(Math.max, "length"), 2); -checkDataDescriptor( - "ArrayBuffer.isView.name", - Object.getOwnPropertyDescriptor(ArrayBuffer.isView, "name"), - "isView", -); - -checkDataDescriptor("Array.name", Object.getOwnPropertyDescriptor(Array, "name"), "Array"); -checkDataDescriptor("Array.length", Object.getOwnPropertyDescriptor(Array, "length"), 1); -checkDataDescriptor("Map.name", Object.getOwnPropertyDescriptor(Map, "name"), "Map"); -checkDataDescriptor("Map.length", Object.getOwnPropertyDescriptor(Map, "length"), 0); -checkDataDescriptor("Date.name", Object.getOwnPropertyDescriptor(Date, "name"), "Date"); -checkDataDescriptor("Date.length", Object.getOwnPropertyDescriptor(Date, "length"), 7); -checkDataDescriptor("DataView.name", Object.getOwnPropertyDescriptor(DataView, "name"), "DataView"); -checkDataDescriptor("DataView.length", Object.getOwnPropertyDescriptor(DataView, "length"), 1); - -if (failures !== 0) { - throw new Error("builtin name/length descriptor parity failed"); -} - -console.log("builtin name/length descriptor parity ok"); -TS - -"$PERRY" compile --no-cache "$TMPDIR/issue_3655.ts" -o "$TMPDIR/issue_3655" \ - >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 - } - -"$TMPDIR/issue_3655" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "builtin name/length descriptor parity ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: builtin name/length descriptor parity" diff --git a/tests/test_issue_3908_tty_write_stream_pipe.sh b/tests/test_issue_3908_tty_write_stream_pipe.sh index 26043fbaf0..41e8bb90bb 100755 --- a/tests/test_issue_3908_tty_write_stream_pipe.sh +++ b/tests/test_issue_3908_tty_write_stream_pipe.sh @@ -3,7 +3,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PERRY="${PERRY_BIN:-${PERRY:-$ROOT/target/release/perry}}" -FIXTURE="$ROOT/tests/issue_3908_tty_write_stream_pipe.js" +FIXTURE="$ROOT/test-parity/node-suite/tty/write-stream-pipe.ts" WORKDIR="${TMPDIR:-/tmp}/perry-issue-3908-$$" BIN="$WORKDIR/perry-tty-pipe" NODE_OUT="$WORKDIR/node.out" diff --git a/tests/test_issue_3989_map_set_semantics.sh b/tests/test_issue_3989_map_set_semantics.sh deleted file mode 100755 index 33a2e415c5..0000000000 --- a/tests/test_issue_3989_map_set_semantics.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/bash -# Regression for #3989: Map/Set constructor iterable semantics and prototype shape. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/debug/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/release/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --bin perry)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -function check(value: boolean, label: string) { - if (!value) { - throw label; - } -} - -const mapClear = Object.getOwnPropertyDescriptor(Map.prototype, "clear"); -check(!!mapClear && typeof mapClear.value === "function", "Map.prototype.clear descriptor"); -check(mapClear.enumerable === false, "Map.prototype.clear non-enumerable"); - -const mapSize = Object.getOwnPropertyDescriptor(Map.prototype, "size"); -check(!!mapSize && typeof mapSize.get === "function", "Map.prototype.size getter"); -let mapSizeThrew = false; -try { - mapSize.get.call({}); -} catch (_e) { - mapSizeThrew = true; -} -check(mapSizeThrew, "Map.prototype.size brand check"); - -const setSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); -check(!!setSize && typeof setSize.get === "function", "Set.prototype.size getter"); -let setSizeThrew = false; -try { - setSize.get.call({}); -} catch (_e) { - setSizeThrew = true; -} -check(setSizeThrew, "Set.prototype.size brand check"); - -check(Object.getPrototypeOf(new Map()) === Map.prototype, "Map instance prototype"); -check(Object.getPrototypeOf(new Set()) === Set.prototype, "Set instance prototype"); -check(Map.prototype.constructor === Map, "Map prototype constructor"); -check(Set.prototype.constructor === Set, "Set prototype constructor"); -check(new Map([[0, 1]]).size === 1, "Map default constructor inserts"); -check(new Set([0]).size === 1, "Set default constructor inserts"); - -const originalMapSet = Map.prototype.set; -let mapSetCalls = 0; -Map.prototype.set = function(_k: any, _v: any) { - mapSetCalls = mapSetCalls + 1; - return this; -}; -const observedMap = new Map([[1, 2], [3, 4]]); -check(mapSetCalls === 2, "Map constructor calls overridden set"); -check(observedMap.size === 0, "Map constructor uses observable set result path"); - -let mapClosed = false; -Map.prototype.set = function(_k: any, _v: any) { - throw "map boom"; -}; -const mapIterable: any = {}; -mapIterable[Symbol.iterator] = function() { - return { - next: function() { - return { done: false, value: [1, 2] }; - }, - return: function() { - mapClosed = true; - return { done: true }; - } - }; -}; -try { - new Map(mapIterable); -} catch (_e) {} -check(mapClosed, "Map constructor closes iterator after set failure"); -Map.prototype.set = originalMapSet; - -const originalSetAdd = Set.prototype.add; -let setAddCalls = 0; -Set.prototype.add = function(_v: any) { - setAddCalls = setAddCalls + 1; - return this; -}; -const observedSet = new Set([1, 2]); -check(setAddCalls === 2, "Set constructor calls overridden add"); -check(observedSet.size === 0, "Set constructor uses observable add result path"); - -let setClosed = false; -Set.prototype.add = function(_v: any) { - throw "set boom"; -}; -const setIterable: any = {}; -setIterable[Symbol.iterator] = function() { - return { - next: function() { - return { done: false, value: 1 }; - }, - return: function() { - setClosed = true; - return { done: true }; - } - }; -}; -try { - new Set(setIterable); -} catch (_e) {} -check(setClosed, "Set constructor closes iterator after add failure"); -Set.prototype.add = originalSetAdd; - -console.log("ok"); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null -RUN_OUTPUT=$(./test_bin 2>&1) - -if [ "$RUN_OUTPUT" = "ok" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: unexpected output" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_4036_async_try_catch_after_await.sh b/tests/test_issue_4036_async_try_catch_after_await.sh deleted file mode 100755 index 2a9f68ab8b..0000000000 --- a/tests/test_issue_4036_async_try_catch_after_await.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash -# Regression for #4036: a try/catch that appears after an earlier await in an -# async function must still receive synchronous throws from the post-await -# continuation. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY:-$REPO_ROOT/target/release/perry}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build --release -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -COMPILE_ENV=(env PERRY_ALLOW_UNIMPLEMENTED=1) -if [[ -f "$REPO_ROOT/target/debug/libperry_runtime.a" || -f "$REPO_ROOT/target/release/libperry_runtime.a" ]]; then - COMPILE_ENV=(env PERRY_ALLOW_UNIMPLEMENTED=1 PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -function boom() { - throw "call-boom"; -} - -function syncCatch() { - try { - throw "sync-boom"; - } catch (e: any) { - console.log("sync caught:", e); - } -} - -async function noPriorAwait() { - try { - throw "no-await-boom"; - } catch (e: any) { - console.log("no-await caught:", e); - } -} - -async function directPostAwait() { - await 0; - try { - throw "direct-boom"; - } catch (e: any) { - console.log("direct caught:", e); - } -} - -async function callPostAwait() { - await 0; - try { - boom(); - } catch (e: any) { - console.log("call caught:", e); - } -} - -async function promiseResolvePostAwait() { - await Promise.resolve(1); - try { - throw new Error("promise-boom"); - } catch (e: any) { - console.log("promise caught:", e.message); - } -} - -async function rejectedAwaitStillWorks() { - try { - await Promise.reject("reject-boom"); - console.log("reject unexpectedly resolved"); - } catch (e: any) { - console.log("reject caught:", e); - } -} - -syncCatch(); -await noPriorAwait(); -await directPostAwait(); -await callPostAwait(); -await promiseResolvePostAwait(); -await rejectedAwaitStillWorks(); -console.log("done"); -EOF - -"${COMPILE_ENV[@]}" "$PERRY" compile --no-cache "$TMPDIR/main.ts" -o "$TMPDIR/test_bin" \ - >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 - } - -RUN_OUTPUT="$("$TMPDIR/test_bin" 2>&1)" - -EXPECTED="sync caught: sync-boom -no-await caught: no-await-boom -direct caught: direct-boom -call caught: call-boom -promise caught: promise-boom -reject caught: reject-boom -done" - -if [[ "$RUN_OUTPUT" == "$EXPECTED" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: async post-await try/catch output changed" -echo "Expected:" -echo "$EXPECTED" -echo -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_4120_math_random_bind.sh b/tests/test_issue_4120_math_random_bind.sh deleted file mode 100755 index 2cdb3b1601..0000000000 --- a/tests/test_issue_4120_math_random_bind.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/issue_4120.ts" <<'TS' -const boundRandom = Math.random.bind(Math); - -console.log(typeof boundRandom); -console.log(typeof boundRandom()); - -let threwTypeError = false; -try { - Function.prototype.bind.call({}); -} catch (error) { - threwTypeError = error instanceof TypeError; -} - -console.log(threwTypeError); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$TMPDIR/issue_4120.ts" -o "$TMPDIR/issue_4120" \ - >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 - } - -"$TMPDIR/issue_4120" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -EXPECTED=$'function\nnumber\ntrue' -ACTUAL="$(cat "$TMPDIR/run.log")" -if [[ "$ACTUAL" != "$EXPECTED" ]]; then - echo "FAIL: unexpected output" - echo "expected:" - printf '%s\n' "$EXPECTED" | sed 's/^/ /' - echo "actual:" - printf '%s\n' "$ACTUAL" | sed 's/^/ /' - exit 1 -fi - -echo "PASS: Math.random.bind compatibility" diff --git a/tests/test_issue_4150_array_property_index_set.sh b/tests/test_issue_4150_array_property_index_set.sh deleted file mode 100755 index 63439133f5..0000000000 --- a/tests/test_issue_4150_array_property_index_set.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/array_property_index_set.ts" <<'TS' -interface State { - width: number - mirror: number[] -} - -function write(state: State, cell: number, value: number): void { - state.mirror[cell] = value -} - -const state: State = { width: 20, mirror: new Array(40) } -write(state, 0, 112) -write(state, 1, 101) -state.mirror[2] = 114 - -if (state.mirror[0] !== 112 || state.mirror[1] !== 101 || state.mirror[2] !== 114) { - throw new Error( - `expected property array indexed writes to round-trip, got ${state.mirror[0]}, ${state.mirror[1]}, ${state.mirror[2]}`, - ) -} - -console.log("array property index set ok") -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$TMPDIR/array_property_index_set.ts" \ - -o "$TMPDIR/array_property_index_set" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$TMPDIR/array_property_index_set" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "array property index set ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: array property index set" diff --git a/tests/test_issue_4150_derived_constructor_arrow_new.sh b/tests/test_issue_4150_derived_constructor_arrow_new.sh deleted file mode 100755 index 2559ecaf2a..0000000000 --- a/tests/test_issue_4150_derived_constructor_arrow_new.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/derived_constructor_arrow_new.ts" <<'TS' -class Base { -} - -class Cache extends Base { - constructor() { - super(); - } -} - -const factory = () => new Cache(); - -const value = factory(); -if (!(value instanceof Cache)) { - throw new Error("expected Cache instance"); -} - -console.log("derived constructor arrow new ok"); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$TMPDIR/derived_constructor_arrow_new.ts" \ - -o "$TMPDIR/derived_constructor_arrow_new" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$TMPDIR/derived_constructor_arrow_new" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "derived constructor arrow new ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: derived constructor arrow new" diff --git a/tests/test_issue_4150_new_array_index_set.sh b/tests/test_issue_4150_new_array_index_set.sh deleted file mode 100755 index 810a38afd4..0000000000 --- a/tests/test_issue_4150_new_array_index_set.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/new_array_index_set.ts" <<'TS' -const values = new Array(4); -values[0] = 112; -values[1] = 101; - -if (values.length !== 4) { - throw new Error("expected preserved array length"); -} -if (values[0] !== 112 || values[1] !== 101) { - throw new Error(`expected indexed writes to round-trip, got ${values[0]}, ${values[1]}`); -} -if (values[2] !== undefined) { - throw new Error("expected unwritten new Array slot to remain undefined"); -} - -console.log("new Array index set ok"); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$TMPDIR/new_array_index_set.ts" \ - -o "$TMPDIR/new_array_index_set" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$TMPDIR/new_array_index_set" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "new Array index set ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: new Array index set" diff --git a/tests/test_issue_4509_numeric_enum_reverse_mapping.sh b/tests/test_issue_4509_numeric_enum_reverse_mapping.sh deleted file mode 100755 index c0156634cf..0000000000 --- a/tests/test_issue_4509_numeric_enum_reverse_mapping.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Issue #4509: TypeScript numeric enums generate a reverse mapping -# (`E[value] === "Name"`). Perry emitted the forward members but the -# reverse lookup returned `undefined` — a silent miscompile. This test -# pins both directions: numeric enums reverse-map (dynamic + literal -# index), the forward computed form still works, and string enums stay -# one-directional (no reverse entry), matching tsc. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/enum_reverse.ts" <<'TS' -enum Color { Red, Green, Blue } -enum Dir { Up = 1, Down } -enum S { A = "aaa", B = "bbb" } - -const c: Color = Color.Blue; - -// Reverse mapping (numeric): dynamic index and literal indices. -if (Color[c] !== "Blue") throw new Error(`reverse dyn: ${Color[c]}`); -if (Color[0] !== "Red" || Color[1] !== "Green" || Color[2] !== "Blue") { - throw new Error(`reverse lit: ${Color[0]},${Color[1]},${Color[2]}`); -} -if (Dir[1] !== "Up" || Dir[2] !== "Down") { - throw new Error(`reverse explicit-base: ${Dir[1]},${Dir[2]}`); -} - -// Forward mapping still works through the computed form. -if ((Color["Blue"] as number) !== 2 || (Dir["Down"] as number) !== 2) { - throw new Error(`forward computed: ${Color["Blue"]},${Dir["Down"]}`); -} - -// Forward `.Member` constant fold is unaffected. -if (Color.Blue !== 2 || Dir.Down !== 2) { - throw new Error(`forward member: ${Color.Blue},${Dir.Down}`); -} - -// String enums are one-directional — no reverse entry. -if (S.A !== "aaa" || S["B"] !== "bbb") { - throw new Error(`string forward: ${S.A},${S["B"]}`); -} -if ((S as any)["aaa"] !== undefined) { - throw new Error(`string reverse should be undefined: ${(S as any)["aaa"]}`); -} - -// Out-of-range numeric reverse lookup is undefined. -if ((Color as any)[99] !== undefined) { - throw new Error(`oob reverse should be undefined: ${(Color as any)[99]}`); -} - -console.log("numeric enum reverse mapping ok"); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$TMPDIR/enum_reverse.ts" \ - -o "$TMPDIR/enum_reverse" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$TMPDIR/enum_reverse" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "numeric enum reverse mapping ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: numeric enum reverse mapping" diff --git a/tests/test_issue_6728_async_gen_await_in_conditional.sh b/tests/test_issue_6728_async_gen_await_in_conditional.sh deleted file mode 100755 index 660ea8465e..0000000000 --- a/tests/test_issue_6728_async_gen_await_in_conditional.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash -# Regression coverage for #6728 / #6709 completion: an `await` nested inside an -# `if` (or any control-flow construct whose branch has no `yield`) in an -# async generator must SUSPEND on the microtask queue, not busy-wait. This is -# pi's EventStream push/pull async iterator shape: -# while (true) { -# if (queue.length === 0) { if (done) return; await producerPromise; } -# while (queue.length > 0) yield queue.shift(); -# } -# Before the fix the `await` inside the `if` was never split into a suspend -# state (the split gate only checked for `yield`), so it deadlocked whenever the -# awaited Promise was settled by an external producer (here: a timer). - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -COMPILE_ENV=() -if [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; then - COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -class EventStream { - queue: string[] = []; - resolve: (() => void) | null = null; - done = false; - push(e: string) { this.queue.push(e); if (this.resolve) { const r = this.resolve; this.resolve = null; r(); } } - async *[Symbol.asyncIterator](): AsyncGenerator { - while (true) { - if (this.queue.length === 0) { // await nested in `if` — no yield here - if (this.done) return; - await new Promise((r) => { this.resolve = r; }); // settled by push() from a timer - } - while (this.queue.length > 0) yield this.queue.shift() as string; - } - } -} -const stream = new EventStream(); -(async () => { - for await (const e of stream) { - console.log("event " + e); - if (e === "last") { console.log("got last"); process.exit(0); } - } -})(); -setTimeout(() => stream.push("working"), 50); -setTimeout(() => stream.push("token"), 100); -setTimeout(() => stream.push("last"), 150); -setTimeout(() => { console.log("GUARD (deadlocked)"); process.exit(2); }, 3000); -EOF - -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) -RUN_EXIT=$? - -EXPECTED="event working -event token -event last -got last" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ] && [ "$RUN_EXIT" -eq 0 ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: async-generator await-in-conditional deadlocked (#6728/#6709 regression)" -echo "Expected (exit 0):" -echo "$EXPECTED" -echo "" -echo "Got (exit $RUN_EXIT):" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_6728_class_field_async_arrow.sh b/tests/test_issue_6728_class_field_async_arrow.sh deleted file mode 100755 index 9342a7a3ed..0000000000 --- a/tests/test_issue_6728_class_field_async_arrow.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash -# Regression coverage for #6728: a CLASS-FIELD-INITIALIZER async arrow whose body -# contains `await` must run its body when called. Before the fix its async-step -# state locals were not boxed (the module-wide boxed-var scan skipped class field -# initializers), so calling it ran no body and the `await` on it resolved with no -# effect. This is pi's `_handleAgentEvent` shape — agent events never rendered. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -COMPILE_ENV=() -if [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; then - COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -class Session { - steering: string[] = []; - // class-field async arrow that uses `this`, an await, and a nested `if` - handle = async (event: any) => { - console.log("handle-enter " + event.type); - if (event.type === "msg" && this.steering.length > 0) { - this.steering.pop(); - } - await Promise.resolve(1); - console.log("handle-after-await " + event.type); - }; -} -// call it directly, via a reference, and via a Set (mirrors the listener path) -(async () => { - const s = new Session(); - await s.handle({ type: "a" }); - const ref = s.handle; - await ref({ type: "b" }); - const listeners = new Set([s.handle]); - for (const l of listeners) await l({ type: "msg" }); - console.log("done"); - process.exit(0); -})(); -EOF - -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) -RUN_EXIT=$? - -EXPECTED="handle-enter a -handle-after-await a -handle-enter b -handle-after-await b -handle-enter msg -handle-after-await msg -done" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ] && [ "$RUN_EXIT" -eq 0 ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: class-field async-arrow body did not run (#6728 regression)" -echo "Expected (exit 0):" -echo "$EXPECTED" -echo "" -echo "Got (exit $RUN_EXIT):" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_6728_multi_await_throw_no_orphan_rejection.sh b/tests/test_issue_6728_multi_await_throw_no_orphan_rejection.sh deleted file mode 100755 index dc2bfe02d0..0000000000 --- a/tests/test_issue_6728_multi_await_throw_no_orphan_rejection.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# Regression coverage for #6728: an async function that reaches a `throw` after -# TWO-OR-MORE `await`s must reject exactly ONE promise (its result, caught by a -# surrounding try/catch). Before the fix, each await past the first minted an -# intermediate then-result promise that adopted the throw's rejection with no -# handler attached, surfacing a spurious "Uncaught (in promise)" and exiting the -# process non-zero — even though the throw was already handled. This is the pi -# interactive-TUI blocker (the Anthropic SDK's makeRequest throws its error -# after many awaits). - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -COMPILE_ENV=() -if [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; then - COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -function sleep(ms: number): Promise { - return new Promise((r) => setTimeout(r, ms)); -} - -// >=2 awaits before the throw is the trigger; 0 or 1 await never orphaned. -async function twoAwaitsThenThrow(): Promise { - await sleep(1); - await sleep(1); - throw new Error("boom"); -} - -// Nested async layer (mirrors the SDK: makeRequest awaited by a caller). -async function callLayer(): Promise { - await twoAwaitsThenThrow(); -} - -async function main(): Promise { - try { - await callLayer(); - console.log("no throw (wrong)"); - } catch (e: any) { - console.log("caught " + (e && e.message)); - } - console.log("done"); -} - -main(); -EOF - -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) -RUN_EXIT=$? - -EXPECTED="caught boom -done" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ] && [ "$RUN_EXIT" -eq 0 ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: multi-await throw produced an orphaned rejection (#6728 regression)" -echo "Expected (exit 0):" -echo "$EXPECTED" -echo "" -echo "Got (exit $RUN_EXIT):" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_issue_undici_from_char_code_apply.sh b/tests/test_issue_undici_from_char_code_apply.sh deleted file mode 100755 index f2de9535b9..0000000000 --- a/tests/test_issue_undici_from_char_code_apply.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# Regression for Undici's CJS `isomorphicDecode` path: -# `String.fromCharCode.apply(null, Uint8Array)` must lower without recursively -# re-entering the namespace-static `.apply` intrinsic and must materialize the -# typed-array bytes as call arguments. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY:-$REPO_ROOT/target/release/perry}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build --release -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat > "$TMPDIR/main.js" << 'EOF' -'use strict' - -const bytes = new Uint8Array([72, 101, 108, 108, 111]) -const viaApply = String.fromCharCode.apply(null, bytes) -const viaSpread = String.fromCharCode(...bytes) -const min = Math.min.apply(null, new Uint8Array([3, 1, 2])) - -if (viaApply !== 'Hello' || viaSpread !== 'Hello' || min !== 1) { - console.log('bad:' + viaApply + ':' + viaSpread + ':' + min) - process.exit(1) -} - -console.log('ok:' + viaApply + ':' + min) -EOF - -"$PERRY" compile --no-cache "$TMPDIR/main.js" -o "$TMPDIR/test_bin" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$TMPDIR/test_bin" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: runtime failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q '^ok:Hello:1$' "$TMPDIR/run.log"; then - echo "FAIL: unexpected output" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: Undici String.fromCharCode.apply typed-array regression" diff --git a/tests/test_method_value_snapshot_bind.sh b/tests/test_method_value_snapshot_bind.sh deleted file mode 100755 index e15146122c..0000000000 --- a/tests/test_method_value_snapshot_bind.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A method read as a value (`const f = obj.m`) must snapshot the method at READ -# time and keep invoking it even after `obj.m` is later reassigned. The canonical -# trigger is the ubiquitous `this.m = this.m.bind(this)` (zod's `ZodType` ctor, -# React class components, …): the own property `m` becomes the bound function -# whose target is the captured value, so re-resolving `m` by name at call time -# found the own property and recursed until the call-depth guard returned the -# null object — surfacing as `obj.m()` yielding `[object Object]` / `undefined`. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/m.ts" <<'TS' -// 1. self-shadowing bind on an OWN method — the zod `ZodType` ctor pattern. -class A { tag = "a"; m() { return "M:" + this.tag; } constructor() { (this as any).m = (this as any).m.bind(this); } } -if (new A().m() !== "M:a") throw new Error("self-bind own: " + new A().m()); - -// 2. self-shadowing bind on an INHERITED method (ZodString extends ZodType). -class Base { m() { return "B:" + (this as any).tag; } constructor() { (this as any).m = (this as any).m.bind(this); } } -class Child extends Base { tag = "c"; } -if (new Child().m() !== "B:c") throw new Error("self-bind inherited: " + new Child().m()); - -// 3. capturing `this.m` then reassigning the own property keeps the method. -class C { tag = "t"; r(): string { const captured = (this as any).m; (this as any).m = "SHADOW"; return captured(); } m() { return "C:" + this.tag; } } -if (new C().r() !== "C:t") throw new Error("this-snapshot regressed: " + new C().r()); - -// 4. a real own-property ARROW override is STILL honored (no regression). -class D { tag = "d"; constructor() { (this as any).m = () => "OWN:" + this.tag; } m() { return "PROTO"; } } -if (new D().m() !== "OWN:d") throw new Error("arrow override regressed: " + new D().m()); - -// 5. plain Function.prototype.bind still works. -function plain(this: any, x: number) { return this.base + x; } -if (plain.bind({ base: 100 })(5) !== 105) throw new Error("plain bind"); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/m.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: method-value snapshot + self-bind" diff --git a/tests/test_namespace_const_cross_module.sh b/tests/test_namespace_const_cross_module.sh deleted file mode 100755 index 6b518fba8c..0000000000 --- a/tests/test_namespace_const_cross_module.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A TS `namespace`'s `export const` members must be accessible when the namespace -# is imported into ANOTHER module. Namespace FUNCTIONS lowered as static methods -# already crossed the module boundary, but `export const` members lived only in -# the defining module's per-module `namespace_vars`, so an importer read them back -# as undefined/garbage. zod's `util` namespace (`util.objectKeys`, …) is imported -# this way, so `_getCached` got null keys and every `z.object({...}).parse()` -# silently dropped all fields. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/util_mod.ts" <<'TS' -export namespace util { - export function fn() { return "FN"; } - export const objectKeys = (obj: any) => Object.keys(obj); - export const arrow = () => "ARROW"; - export const num = 42; -} -TS -cat >"$TMPDIR/main.ts" <<'TS' -import { util } from "./util_mod.js"; -if (util.fn() !== "FN") throw new Error("fn: " + util.fn()); -if (util.arrow() !== "ARROW") throw new Error("arrow: " + util.arrow()); -if (util.num !== 42) throw new Error("num: " + util.num); -if (JSON.stringify(util.objectKeys({ a: 1, b: 2 })) !== '["a","b"]') { - throw new Error("objectKeys: " + JSON.stringify(util.objectKeys({ a: 1, b: 2 }))); -} -// assigning the member to a local and calling it must also work -const ok = util.objectKeys; -if (JSON.stringify(ok({ x: 1 })) !== '["x"]') throw new Error("via local: " + JSON.stringify(ok({ x: 1 }))); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: namespace const cross-module access" diff --git a/tests/test_namespace_import_enumerable.sh b/tests/test_namespace_import_enumerable.sh deleted file mode 100755 index c2498bb337..0000000000 --- a/tests/test_namespace_import_enumerable.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A namespace import (`import * as ns from "./m.js"`) used as a whole VALUE must -# be a real object whose OWN ENUMERABLE properties are the source module's -# RUNTIME exports — so `Object.keys(ns)`, `for…in ns`, `Object.entries(ns)`, -# `hasOwnProperty`, and passing `ns` to a library all see the members. Pre-fix -# the namespace value lowered to an empty `js_unresolved_namespace_stub`, so -# `Object.keys` was `[]` (drizzle's `drizzle(pool, { schema })` where -# `import * as schema` saw zero tables; Stripe's `_prepResources` -# `for (const n in resources)` attached nothing). -# -# Type-only exports (`export type` / `export interface`) are erased at runtime -# and must NOT appear among the enumerable members (they have no runtime value). - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/schema.ts" <<'TS' -export const alpha = { id: 1 }; -export const beta = { id: 2 }; -export const gammaEnum = ["x", "y"] as const; -export function helper() { return "H"; } -// Type-only exports — erased at runtime, must not be enumerable members. -export type Alpha = typeof alpha; -export type Beta = typeof beta; -export interface Thing { id: number } -TS -cat >"$TMPDIR/main.ts" <<'TS' -import * as schema from "./schema.js"; - -const keys = Object.keys(schema).sort(); -if (JSON.stringify(keys) !== '["alpha","beta","gammaEnum","helper"]') { - throw new Error("keys: " + JSON.stringify(keys)); -} -// for…in matches. -const forIn: string[] = []; -for (const k in schema) forIn.push(k); -if (JSON.stringify(forIn.sort()) !== '["alpha","beta","gammaEnum","helper"]') { - throw new Error("for-in: " + JSON.stringify(forIn)); -} -// hasOwnProperty + Object.entries. -if (!Object.prototype.hasOwnProperty.call(schema, "alpha")) throw new Error("hasOwn alpha"); -if (Object.prototype.hasOwnProperty.call(schema, "Alpha")) throw new Error("type leaked: Alpha"); -const entries = Object.entries(schema).map(([k]) => k).sort(); -if (entries.length !== 4) throw new Error("entries: " + JSON.stringify(entries)); -// Members still resolve to their real values + the direct `ns.member` read works. -if ((schema as any).alpha.id !== 1) throw new Error("alpha.id: " + (schema as any).alpha.id); -if ((schema as any).helper() !== "H") throw new Error("helper(): " + (schema as any).helper()); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: namespace import is an enumerable runtime-export object" diff --git a/tests/test_namespace_reexport_binding.sh b/tests/test_namespace_reexport_binding.sh deleted file mode 100755 index f9b6b0f432..0000000000 --- a/tests/test_namespace_reexport_binding.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Re-exporting an `import * as ns` namespace binding via a bare `export { ns }` -# must hand the importer the namespace OBJECT (with all its members), not a bare -# function symbol. Before the fix, `export { z }` of a namespace local lowered to -# `Export::Named`, so a consumer's `import { z }` resolved `z` to a function and -# every `z.` was `undefined`. This is exactly how zod re-exports `z` -# (`import * as z from "./external"; export { z }`), so it broke all of zod. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -# Mirrors zod's shape: a target module using `export *` + a direct export, a -# barrel that does `import * as z` then `export { z }` (and `export default z`). -cat >"$TMPDIR/sub.ts" <<'TS' -export function object() { return "obj"; } -TS -cat >"$TMPDIR/external.ts" <<'TS' -export * from "./sub.js"; -export const coerce = { number: () => 42 }; -TS -cat >"$TMPDIR/barrel.ts" <<'TS' -import * as z from "./external.js"; -export * from "./external.js"; -export { z }; -export default z; -TS -cat >"$TMPDIR/main.ts" <<'TS' -import { z } from "./barrel.js"; -if (typeof z !== "object") throw new Error(`typeof z: ${typeof z}`); -if (typeof (z as any).object !== "function") throw new Error("z.object missing"); -if (typeof (z as any).coerce !== "object") throw new Error("z.coerce missing"); -if ((z as any).coerce.number() !== 42) throw new Error("z.coerce.number() wrong"); -if ((z as any).object() !== "obj") throw new Error("z.object() wrong"); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { - echo "FAIL: perry run errored" - echo "$OUT" - exit 1 -} - -if ! grep -q "^OK$" <<<"$OUT"; then - echo "FAIL: expected OK, got:" - echo "$OUT" - exit 1 -fi - -echo "PASS: namespace re-export binding" diff --git a/tests/test_native_class_semantics_reduced.sh b/tests/test_native_class_semantics_reduced.sh deleted file mode 100755 index 9dafc401a5..0000000000 --- a/tests/test_native_class_semantics_reduced.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reduced Three-style native class semantics coverage: -# - TypeScript `declare` class properties are type-only and must not create -# instance fields or static slots -# - an inherited setter-only `needsUpdate` accessor on a DataTexture-shaped -# module boundary increments source.version, while reads remain undefined - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/source.ts" <<'TS' -export class SourceLike { - version = 0; - - set needsUpdate(value: boolean) { - if (value === true) { - this.version++; - } - } -} -TS - -cat >"$TMPDIR/texture.ts" <<'TS' -import { SourceLike } from "./source.js"; - -export class TextureLike { - source = new SourceLike(); - - set needsUpdate(value: boolean) { - this.source.needsUpdate = value; - } -} -TS - -cat >"$TMPDIR/data_texture.ts" <<'TS' -import { TextureLike } from "./texture.js"; - -export class DataTextureLike extends TextureLike { - declare readonly isDataTexture: true; - declare static readonly DEFAULT_IMAGE: unknown; - - dataTag = "data-texture"; -} -TS - -cat >"$TMPDIR/main.ts" <<'TS' -import { DataTextureLike } from "./data_texture.js"; - -function hasOwn(value: any, key: string): boolean { - return Object.prototype.hasOwnProperty.call(value, key); -} - -const texture: any = new DataTextureLike(); -if (texture.dataTag !== "data-texture") throw new Error("dataTag: " + texture.dataTag); -if (texture.source.version !== 0) throw new Error("initial version: " + texture.source.version); -if (texture.needsUpdate !== undefined) throw new Error("setter-only read should be undefined"); -texture.needsUpdate = true; -if (texture.source.version !== 1) throw new Error("version after true: " + texture.source.version); -texture.needsUpdate = false; -if (texture.source.version !== 1) throw new Error("version after false: " + texture.source.version); -texture.needsUpdate = true; -if (texture.source.version !== 2) throw new Error("version after second true: " + texture.source.version); -if (hasOwn(texture, "needsUpdate")) throw new Error("setter write created own data field"); -if (hasOwn(texture, "isDataTexture")) throw new Error("DataTexture declare field leaked"); -if (hasOwn(DataTextureLike, "DEFAULT_IMAGE")) throw new Error("DataTexture declare static leaked"); - -console.log("OK"); -TS - -OUT="$(PERRY_NO_AUTO_OPTIMIZE=1 "$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { - echo "FAIL: perry run errored" - echo "$OUT" - exit 1 -} -if ! grep -q "^OK$" <<<"$OUT"; then - echo "FAIL: expected OK, got:" - echo "$OUT" - exit 1 -fi -echo "PASS: reduced native class semantics" diff --git a/tests/test_native_value_profile.sh b/tests/test_native_value_profile.sh deleted file mode 100755 index 2aadfcbb55..0000000000 --- a/tests/test_native_value_profile.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# End-to-end regression for issue #6827's first public native-value slice: -# `perry/native` imports must resolve without a runtime module and lower to the -# existing verifier-backed POD layout and NativeArena intrinsics. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$SCRIPT_DIR/.." -TEST_TMPDIR=$(mktemp -d) -trap 'rm -rf "$TEST_TMPDIR"' EXIT - -if [ -z "${PERRY:-}" ]; then - cargo build -q -p perry - PERRY="$REPO_ROOT/target/debug/perry" -fi - -case "$PERRY" in - /*) ;; - *) PERRY="$(pwd)/$PERRY" ;; -esac - -if [ ! -x "$PERRY" ]; then - echo "FAIL: perry binary not found at $PERRY" - exit 1 -fi - -cp "$SCRIPT_DIR/fixtures/native_value_profile.ts" "$TEST_TMPDIR/main.ts" - -cd "$TEST_TMPDIR" -"$PERRY" compile main.ts --output native_value_profile --no-cache >/dev/null - -EXPECTED="size=24,align=8,sequence=8,length=1,flags=4294967295,sequenceValue=9007199254740991,gainRounded=true,tiny=2:1:255:255:7,narrow=16:2:4:8:-128:65535:-32768:-9007199254740991:-5:65535:-1024:-42,header=7:42:true,rejectedFraction=true,rejectedType=true,rejectedOctet=true,rejectedSignedByte=true,rejectedHalfWord=true,rejectedSignedHalfWord=true,rejectedSignedSize=true" -ACTUAL=$(./native_value_profile) -if [ "$ACTUAL" != "$EXPECTED" ]; then - echo "FAIL: perry/native output mismatch" - echo "Expected: $EXPECTED" - echo "Actual: $ACTUAL" - exit 1 -fi - -echo "PASS" diff --git a/tests/test_object_assign_function_source.sh b/tests/test_object_assign_function_source.sh deleted file mode 100755 index d217372082..0000000000 --- a/tests/test_object_assign_function_source.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `Object.assign(target, fn)` where the source is a function/closure must copy -# the function's own *enumerable* statics (and skip the non-enumerable -# `length`/`name`/`prototype` slots) without hanging. A function is a -# ClosureHeader, not an ObjectHeader; reading `keys_array` off it yielded a -# garbage key_count and a runaway copy loop. Stripe's `protoExtend` does -# `Object.assign(Constructor, Super)` to copy a resource class's enumerable -# statics — this hung at `import 'stripe'`. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -function Super(this: any, a: any) { this._a = a; } -(Super as any).extend = "EXT"; -(Super as any).method = "METH"; -(Super as any).MAX = 100; -// Reassigning `.prototype` to a plain object is what Stripe's protoExtend does. -(Super as any).prototype = { _a: null, path: "", initialize() {}, foo() { return 1; } }; - -const t: any = {}; -Object.assign(t, Super); -// Enumerable statics copied: -if (t.extend !== "EXT") throw new Error("extend: " + t.extend); -if (t.method !== "METH") throw new Error("method: " + t.method); -if (t.MAX !== 100) throw new Error("MAX: " + t.MAX); -// Non-enumerable `prototype`/`name`/`length` must NOT be copied. -const keys = Object.keys(t).sort(); -if (JSON.stringify(keys) !== '["MAX","extend","method"]') throw new Error("keys: " + JSON.stringify(keys)); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: Object.assign with a function source copies enumerable statics" diff --git a/tests/test_object_ctor_inherited_proto_methods.sh b/tests/test_object_ctor_inherited_proto_methods.sh deleted file mode 100755 index b6597969a0..0000000000 --- a/tests/test_object_ctor_inherited_proto_methods.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `Object` is a function, so reading a non-static member resolves up its -# prototype chain — `Object.hasOwnProperty` IS `Object.prototype.hasOwnProperty`, -# a callable. immer's `O.hasOwnProperty.call(proto, "constructor")` (with -# `const O = Object`) depends on this. Before the fix, reading `hasOwnProperty` -# (and `isPrototypeOf` / `propertyIsEnumerable` / `toString` / `valueOf` / -# `toLocaleString`) off the reified `Object` constructor value returned -# `undefined`, so `.call(...)` threw "Function.prototype.call was called on a -# value that is not a function". - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -const O: any = Object; -if (typeof O.hasOwnProperty !== "function") throw new Error("hasOwnProperty not a function"); -if (typeof O.isPrototypeOf !== "function") throw new Error("isPrototypeOf not a function"); -if (typeof O.propertyIsEnumerable !== "function") throw new Error("propertyIsEnumerable not a function"); -const proto = O.getPrototypeOf({ a: 1 }); -if (O.hasOwnProperty.call(proto, "constructor") !== true) throw new Error("call(constructor) wrong"); -if (O.hasOwnProperty.call(proto, "nope") !== false) throw new Error("call(nope) wrong"); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: Object constructor exposes inherited Object.prototype methods" diff --git a/tests/test_object_literal_getter_this_binding.sh b/tests/test_object_literal_getter_this_binding.sh deleted file mode 100755 index 7ccca769a1..0000000000 --- a/tests/test_object_literal_getter_this_binding.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# An object-literal accessor (`{ get k() {...} }`) is a REGULAR (non-arrow) -# function: `this` binds dynamically to the receiver at call time, NOT to the -# object the accessor is defined on. The HIR lowered it with `captures_this: -# true`, capturing `this` at object-construction time — so an INHERITED read -# (`Object.create(proto).k`, where the getter lives on `proto`) saw -# `this === proto` instead of the instance, and `this[k]` / `this.field` came -# back undefined. @hono/node-server's request prototype -# (`get method() { return this[incomingKey].method }`) crashed on every request. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -const k = Symbol("k"); -const proto: any = { - get method() { return (this as any)[k] ? (this as any)[k].m : "GET"; }, - get plusOne() { return (this as any).x + 1; }, -}; -// Inherited read: `this` must be the instance, not `proto`. -const inst: any = Object.create(proto); -inst[k] = { m: "POST" }; -inst.x = 41; -if (inst.method !== "POST") throw new Error("inherited symbol this: " + inst.method); -if (inst.plusOne !== 42) throw new Error("inherited field this: " + inst.plusOne); - -// A second instance off the same prototype sees its OWN state (no static capture). -const inst2: any = Object.create(proto); -inst2[k] = { m: "PUT" }; -inst2.x = 9; -if (inst2.method !== "PUT") throw new Error("instance2 method: " + inst2.method); -if (inst2.plusOne !== 10) throw new Error("instance2 plusOne: " + inst2.plusOne); - -// Own (non-inherited) accessor still binds `this` to the object. -const own: any = { _v: 5, get v() { return (this as any)._v; } }; -if (own.v !== 5) throw new Error("own accessor this: " + own.v); - -// Setter `this` also binds to the receiver (inherited). -const sproto: any = { set val(v: number) { (this as any)._v = v * 2; }, get val() { return (this as any)._v; } }; -const sinst: any = Object.create(sproto); -sinst.val = 21; -if (sinst.val !== 42) throw new Error("inherited setter this: " + sinst.val); - -// A CLASS getter inherited through `Object.create(instance)` must also bind -// `this` to the leaf object (the runtime walks the prototype chain and the -// vtable getter would otherwise observe the prototype instance). -class A { v = 0; get x() { return (this as any).v + 100; } } -const aInst: any = new A(); -const leaf: any = Object.create(aInst); -leaf.v = 5; -if (leaf.x !== 105) throw new Error("inherited class getter this: " + leaf.x); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: object-literal getter binds this dynamically (inherited + own)" diff --git a/tests/test_object_shorthand_imported_binding.sh b/tests/test_object_shorthand_imported_binding.sh deleted file mode 100755 index 3c64901ee0..0000000000 --- a/tests/test_object_shorthand_imported_binding.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# An object-literal SHORTHAND property whose name is a cross-module imported -# binding (`import { db } from "./client.js"; const ctx = { db, ...rest }`) must -# resolve to the imported value. The HIR shorthand-folding only checked -# local/func/class/builtin bindings, so an imported name fell through and the -# property was silently DROPPED — `{ db }` lowered to an empty object, so -# `getContext().db` was `undefined` and every `db.query.*` read crashed -# ("Cannot read properties of undefined"). Covers all three object-literal -# lowering paths: closed-shape, spread/method IIFE, and the computed-key path. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/client.ts" <<'TS' -export const db = { tag: "DBINST", q: () => 7 }; -export const helper = () => "H"; -TS -cat >"$TMPDIR/main.ts" <<'TS' -import { db, helper } from "./client.js"; - -// 1) closed-shape shorthand -const a: any = { db }; -if (a.db?.tag !== "DBINST") throw new Error("closed-shape: " + JSON.stringify(a.db)); - -// 2) shorthand + sibling (still closed-shape) -const b: any = { db, n: 1 }; -if (b.db?.tag !== "DBINST" || b.n !== 1) throw new Error("sibling: " + JSON.stringify(b)); - -// 3) shorthand + spread (the getContext shape — IIFE path) -const rest = { x: 9 }; -const c: any = { db, helper, ...rest }; -if (c.db?.tag !== "DBINST") throw new Error("spread db: " + JSON.stringify(c.db)); -if (typeof c.helper !== "function" || c.helper() !== "H") throw new Error("spread helper"); -if (c.x !== 9) throw new Error("spread x: " + c.x); - -// 4) shorthand + computed key (computed-key path) -const key = "dyn"; -const d: any = { db, [key]: 5 }; -if (d.db?.tag !== "DBINST" || d.dyn !== 5) throw new Error("computed: " + JSON.stringify(d)); - -// the imported value is fully usable -if (a.db.q() !== 7) throw new Error("method call: " + a.db.q()); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/main.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: object shorthand resolves a cross-module imported binding" diff --git a/tests/test_optional_chain_double_member_call.sh b/tests/test_optional_chain_double_member_call.sh deleted file mode 100755 index cb114a5f32..0000000000 --- a/tests/test_optional_chain_double_member_call.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -# Regression: a double-optional chain that calls a method on the result of an -# upstream optional member access — `a?.b?.method(args)` — threw -# `TypeError: Cannot read properties of undefined (reading 'method')` when the -# upstream `a.b` was `undefined`, instead of short-circuiting to `undefined`. -# -# Root cause: in the HIR optional-call lowering (lower_expr.rs OptChain(Call)), -# when the receiver of an optional method member (`?.method`) is itself produced -# by an upstream optional chain, the receiver lowers to a Conditional. The -# inner-Conditional nesting branch reused the un-short-circuited receiver -# (`a.b`) directly as the call's object WITHOUT re-applying the per-receiver -# null-guard that the non-Conditional path emits — so `(a.b).method(args)` -# dereferenced an `undefined` receiver and threw. The fix re-adds a -# receiver-nullish guard (`a.b == null ? undefined : (a.b).method(args)`) for -# the optional-member case, gated on a side-effect-free receiver. -# -# This is the `_?.allowModels?.some(...)` startup wall. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -const cfg: any = {}; // cfg.allowModels is undefined -const u: any = undefined; -const obj: any = { a: { arr: [1, 2, 3] } }; - -// The wall: optional member access feeding an optional-member method call. -// cfg.allowModels is undefined, so `?.some(...)` must short-circuit, not throw. -const r1 = cfg?.allowModels?.some((m: string) => m === "x"); -console.log("r1=" + (r1 === undefined ? "undefined" : r1)); // undefined - -// Plain undefined receiver via a double chain. -const r2 = u?.missing?.some((x: number) => x > 0); -console.log("r2=" + (r2 === undefined ? "undefined" : r2)); // undefined - -// Positive path: the chain resolves to a real array → method runs. -const r3 = obj?.a?.arr?.some((x: number) => x === 2); -console.log("r3=" + r3); // true - -// Positive path with a transform method (no false short-circuit). -const r4 = obj?.a?.arr?.map((x: number) => x * 2).join(","); -console.log("r4=" + r4); // "2,4,6" - -// Optional-CALL variant: `a?.b?.method?.(args)` where `a.b` is undefined — -// the function-value guard must NOT read `(a.b).method` off the undefined -// receiver (which would throw); the receiver short-circuit comes first. -const r5 = u?.missing?.foo?.((x: number) => x > 0); -console.log("r5=" + (r5 === undefined ? "undefined" : r5)); // undefined - -// Optional-CALL variant, positive: real function value resolves and is called. -const o2: any = { fn: () => 99 }; -const r6 = o2?.fn?.(); -console.log("r6=" + r6); // 99 -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED='r1=undefined -r2=undefined -r3=true -r4=2,4,6 -r5=undefined -r6=99' - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: double-optional member call short-circuit returned wrong value" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_process_env_value.sh b/tests/test_process_env_value.sh deleted file mode 100755 index e1548c5098..0000000000 --- a/tests/test_process_env_value.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `process.env` read as a whole VALUE (not the member form `process.env.X`) must -# materialize the live environment object. Member reads are special-cased to -# `EnvGet`, but passing `process.env` whole — e.g. `Schema.safeParse(process.env)`, -# the canonical config-validation pattern (zod/envalid/…) — reached the GlobalGet -# value fall-through and lowered to `undefined`, so the consumer iterated undefined. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/e.ts" <<'TS' -const env: any = process.env; -if (typeof env !== "object" || env === null) throw new Error("typeof process.env: " + typeof env); -if (env.PERRY_TEST_VAR !== "hello") throw new Error("value: " + env.PERRY_TEST_VAR); -const keys = Object.keys(env); -if (keys.length < 1) throw new Error("no keys"); -// passing process.env whole to a function (the safeParse(process.env) shape) -function count(o: Record): number { return Object.keys(o).length; } -if (count(process.env as any) < 1) throw new Error("passed-whole count 0"); -console.log("OK"); -TS - -OUT="$(PERRY_TEST_VAR=hello "$PERRY" run "$TMPDIR/e.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: process.env as a value" diff --git a/tests/test_proxy_field_ic_miss.sh b/tests/test_proxy_field_ic_miss.sh deleted file mode 100755 index b6cd516989..0000000000 --- a/tests/test_proxy_field_ic_miss.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reading a STRING-keyed property off a Proxy through the monomorphic -# inline-cache *miss* handler (`js_object_get_field_ic_miss`) must route through -# the proxy `get` trap, not pass the proxy's small registered id to -# `closure_dynamic_prop_by_key` -> `is_closure_ptr`, which reads CLOSURE_MAGIC at -# offset 12 of the (fake) pointer and SIGSEGVs. -# -# drizzle-orm's relational query builder wraps each selected column in an -# aliased-column `new Proxy(column, handler)` and then reads string properties -# off it (`col.name`, `col.dataType`, ...) while assembling rows in -# `db.query..findMany()`. Those fused `proxy.field` reads miss the -# per-site inline cache (a Proxy has no stable `keys_array` shape), landing in -# the IC-miss handler with the proxy id as the receiver -> EXC_BAD_ACCESS at a -# proxy-band address like 0xf0024. Refs #4661/#4663 (same proxy-deref family). - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -// A Proxy wrapping a plain object with string-keyed fields, read through a -// forwarding get trap. The repeated reads in a loop drive the per-site inline -// cache to miss on the proxy receiver, exercising js_object_get_field_ic_miss. -const target: any = { name: "id", dataType: "string", notNull: true }; -const proxy: any = new Proxy(target, { - get(t: any, k: any) { return t[k]; }, -}); - -let acc = ""; -for (let i = 0; i < 1000; i++) { - // Fused `proxy.field` string-keyed reads — these miss the inline cache. - acc = String(proxy.name) + ":" + String(proxy.dataType) + ":" + String(proxy.notNull); -} -if (acc !== "id:string:true") throw new Error("string read via proxy: " + acc); - -// No get trap -> forward to target directly through the same miss path. -const p2: any = new Proxy(target, {}); -if (p2.name !== "id") throw new Error("string forward to target: " + String(p2.name)); - -// A missing string key returns undefined (not a crash). -if (proxy.absent !== undefined) throw new Error("absent string key should be undefined"); - -// Class instance behind the proxy, read an instance field by string key. -class Column { name = "col"; sql = "SELECT"; } -const inst: any = new Column(); -const p3: any = new Proxy(inst, { get(t: any, k: any) { return t[k]; } }); -let r = ""; -for (let i = 0; i < 1000; i++) { r = String(p3.name) + "/" + String(p3.sql); } -if (r !== "col/SELECT") throw new Error("instance field via proxy: " + r); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored (segfault?)"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: string-keyed read on a Proxy via IC-miss forwards (no EXC_BAD_ACCESS)" diff --git a/tests/test_proxy_fused_method_call.sh b/tests/test_proxy_fused_method_call.sh deleted file mode 100755 index 26b7db9a38..0000000000 --- a/tests/test_proxy_fused_method_call.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A *fused* method call `proxy.method(arg)` on a Proxy receiver must route the -# call through the proxy (Get(proxy, method) + Call(method, proxy, args)), -# not misclassify the proxy's small registered id as a native-module integer -# handle. Proxy ids encode to small pointer-tagged values in the runtime's -# `< 0x100000` "small handle" band; the generic dynamic dispatcher -# (`js_native_call_method`) used to fall straight into that handle path for a -# fused call, dropping the argument and returning null/undefined. -# -# The decomposed form `const f = proxy.method; f(arg)` always worked (the read -# goes through `js_proxy_get`); only the fused form was broken. drizzle's -# relational-query mapper does `decoder.mapFromDriverValue(value)` where -# `decoder` is an aliased-column proxy, so every `findMany` column came back -# null. Regression for fix/proxy-fused-method-call (follow-up to #4661). - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -// The receiver must be opaque (`any`) and the method reached via the dynamic -// dispatcher for the bug to fire. An own *function-valued property* on the -// target (rather than a statically-resolvable class method) keeps the static -// analyzer from binding the call to a known vtable, so codegen emits the -// generic `js_native_call_method` dispatch — exactly the drizzle shape. -function makeForwardingProxy(): any { - const target: any = { kind: "col" }; - target.run = function (v: any) { return v; }; - return new Proxy(target, { get(t: any, k: any) { return t[k]; } }); -} - -// 1) Forwarding get-trap proxy: fused call must not drop the argument. -const p: any = makeForwardingProxy(); -const fused = p.run("hi"); -if (fused !== "hi") throw new Error("fused get-trap proxy call dropped arg: " + JSON.stringify(fused)); - -// The decomposed form must keep working too. -const f = p.run; const decomp = f("hi"); -if (decomp !== "hi") throw new Error("decomposed proxy call wrong: " + JSON.stringify(decomp)); - -// 2) No get trap -> forward to target; fused call still returns the arg. -function makePassthroughProxy(): any { - const target: any = { kind: "col2" }; - target.echo = function (v: any) { return v; }; - return new Proxy(target, {}); -} -const p2: any = makePassthroughProxy(); -if (p2.echo("yo") !== "yo") throw new Error("fused no-trap proxy call dropped arg: " + JSON.stringify(p2.echo("yo"))); - -// 3) `this` is bound to the proxy inside the fused call. -function makeThisProxy(): any { - const target: any = { tag: "T" }; - target.readTag = function (this: any) { return this.tag; }; - return new Proxy(target, { get(t: any, k: any) { return t[k]; } }); -} -const p3: any = makeThisProxy(); -if (p3.readTag() !== "T") throw new Error("`this` not bound to proxy in fused call: " + JSON.stringify(p3.readTag())); - -// 4) Inherited method through the target's prototype chain (the real drizzle -// case: `mapFromDriverValue` is inherited from a base class). -class Base { mapFromDriverValue(v: any) { return v; } } -class Mid extends Base { x = 1; } -class Leaf extends Mid { y = 2; } -function makeInheritedProxy(): any { - const inst: any = new Leaf(); - return new Proxy(inst, { get(t: any, k: any) { return t[k]; } }); -} -const p4: any = makeInheritedProxy(); -if (p4.mapFromDriverValue("hi") !== "hi") { - throw new Error("fused call to inherited method dropped arg: " + JSON.stringify(p4.mapFromDriverValue("hi"))); -} - -// 5) Multiple args are all forwarded. -function makeSumProxy(): any { - const target: any = {}; - target.add3 = function (a: number, b: number, c: number) { return a + b + c; }; - return new Proxy(target, { get(t: any, k: any) { return t[k]; } }); -} -const p5: any = makeSumProxy(); -if (p5.add3(1, 2, 3) !== 6) throw new Error("fused multi-arg call wrong: " + JSON.stringify(p5.add3(1, 2, 3))); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: fused method call on a Proxy receiver forwards arg + this (not dropped)" diff --git a/tests/test_proxy_getprototypeof_instanceof.sh b/tests/test_proxy_getprototypeof_instanceof.sh deleted file mode 100755 index d462cbf1b8..0000000000 --- a/tests/test_proxy_getprototypeof_instanceof.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A Proxy without a `getPrototypeOf` trap must forward `[[GetPrototypeOf]]` to its -# target, and `proxy instanceof C` must follow that forwarded chain. Perry -# represents a Proxy as a small registered id (not a heap object), so -# `Object.getPrototypeOf(proxy)` returned `null` and `proxy instanceof C` was -# always `false`. Drizzle aliases columns as `new Proxy(column, …)` and its -# brand check `is(value, type)` reads `getPrototypeOf(value).constructor` / does -# `value instanceof type` — the null prototype crashed on `null.constructor`. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -class Base { static tag = "B"; } -class Mid extends Base {} -class Leaf extends Mid { constructor(public n: string) { super(); } } - -const leaf = new Leaf("x"); -const handler = { get(t: any, k: any) { return t[k]; } }; -const proxy: any = new Proxy(leaf, handler); - -// getPrototypeOf forwards to the target's prototype (constructor = the class). -const proto = Object.getPrototypeOf(proxy); -if (proto === null || proto === undefined) throw new Error("getPrototypeOf(proxy) was nullish"); -if (proto.constructor?.name !== "Leaf") throw new Error("proto.constructor: " + proto.constructor?.name); - -// instanceof follows the forwarded chain (target's class hierarchy). -if (!(proxy instanceof Leaf)) throw new Error("proxy not instanceof Leaf"); -if (!(proxy instanceof Mid)) throw new Error("proxy not instanceof Mid"); -if (!(proxy instanceof Base)) throw new Error("proxy not instanceof Base"); -if (!(proxy instanceof Object)) throw new Error("proxy not instanceof Object"); - -// member access still forwards through the get trap. -if (proxy.n !== "x") throw new Error("proxy.n: " + proxy.n); - -// nested proxy (proxy of a proxy) still resolves to the real target. -const proxy2: any = new Proxy(proxy, handler); -if (!(proxy2 instanceof Leaf)) throw new Error("nested proxy not instanceof Leaf"); -if (Object.getPrototypeOf(proxy2)?.constructor?.name !== "Leaf") throw new Error("nested getPrototypeOf"); - -// the `is()`-style brand check no longer crashes on null.constructor. -function is(value: any, type: any): boolean { - if (!value || typeof value !== "object") return false; - if (value instanceof type) return true; - let cls = Object.getPrototypeOf(value)?.constructor; - while (cls) cls = Object.getPrototypeOf(cls); - return false; -} -if (!is(proxy, Base)) throw new Error("is(proxy, Base) was false"); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: proxy getPrototypeOf forwards to target + instanceof follows it" diff --git a/tests/test_proxy_symbol_property.sh b/tests/test_proxy_symbol_property.sh deleted file mode 100755 index 6d21cb6080..0000000000 --- a/tests/test_proxy_symbol_property.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reading a SYMBOL-keyed property off a Proxy must forward through the proxy -# `get` trap (or to the target), not dereference the proxy's small registered id -# as a heap object — which was an EXC_BAD_ACCESS. drizzle's relational-query -# builder reads symbol keys (`col[entityKind]`, `col[Table.Symbol.*]`) off -# aliased-column proxies (`new Proxy(column, …)`), so every `findMany` crashed. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -const tag = Symbol("tag"); -const other = Symbol.for("shared"); - -// forwarding get trap -const target: any = { [tag]: "T", [other]: 42, plain: "p" }; -const proxy: any = new Proxy(target, { get(t: any, k: any) { return t[k]; } }); -if (proxy[tag] !== "T") throw new Error("symbol via get trap: " + String(proxy[tag])); -if (proxy[other] !== 42) throw new Error("Symbol.for via get trap: " + String(proxy[other])); -if (proxy.plain !== "p") throw new Error("string via get trap: " + proxy.plain); - -// no get trap -> forward to target -const p2: any = new Proxy(target, {}); -if (p2[tag] !== "T") throw new Error("symbol forward to target: " + String(p2[tag])); - -// a class instance behind the proxy, read its static-ish symbol off the instance -class C { [tag] = "instTag"; } -const inst: any = new C(); -const p3: any = new Proxy(inst, { get(t: any, k: any) { return t[k]; } }); -if (p3[tag] !== "instTag") throw new Error("instance symbol via proxy: " + String(p3[tag])); - -// missing symbol returns undefined, not a crash -if (proxy[Symbol("absent")] !== undefined) throw new Error("absent symbol should be undefined"); - -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: symbol-keyed read on a Proxy forwards (no EXC_BAD_ACCESS)" diff --git a/tests/test_regex_d_flag.sh b/tests/test_regex_d_flag.sh deleted file mode 100755 index c41ec4ff1a..0000000000 --- a/tests/test_regex_d_flag.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# Test for regex `d` flag (hasIndices) implementation -set -e - -PERRY="./target/release/perry" -TEST_DIR="/tmp/perry_regex_d_test" -mkdir -p "$TEST_DIR" - -# Simple test -cat > "$TEST_DIR/test.ts" << 'EOF' -const re = /hello/d; -const m = re.exec("say hello world"); -console.log("indices:", JSON.stringify(m.indices)); -console.log("hasIndices:", re.hasIndices); -EOF - -echo "Compiling test..." -$PERRY "$TEST_DIR/test.ts" -o "$TEST_DIR/test" 2>&1 | tail -5 - -echo "" -echo "Running test..." -"$TEST_DIR/test" - -echo "" -echo "Test completed!" diff --git a/tests/test_regex_d_flag_comprehensive.sh b/tests/test_regex_d_flag_comprehensive.sh deleted file mode 100755 index ae55b1cb01..0000000000 --- a/tests/test_regex_d_flag_comprehensive.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# Comprehensive test for regex `d` flag (hasIndices) implementation -set -e - -PERRY="./target/release/perry" -TEST_DIR="/tmp/perry_regex_d_test" -mkdir -p "$TEST_DIR" - -# Test 1: Basic indices -cat > "$TEST_DIR/test1.ts" << 'EOF' -const re = /hello/d; -const m = re.exec("say hello world"); -console.log("Test 1: Basic indices"); -console.log(" indices:", JSON.stringify(m.indices)); -console.log(" Expected: [[4,9]]"); -console.log(" Pass:", JSON.stringify(m.indices) === "[[4,9]]"); -EOF - -# Test 2: Capture groups -cat > "$TEST_DIR/test2.ts" << 'EOF' -const re = /(\w+)@(\w+)/d; -const m = re.exec("email: test@example"); -console.log("Test 2: Capture groups"); -console.log(" indices:", JSON.stringify(m.indices)); -console.log(" Expected: [[7,19], [7,11], [12,19]]"); -EOF - -# Test 3: Named groups -cat > "$TEST_DIR/test3.ts" << 'EOF' -const re = /(?\d{4})-(?\d{2})/d; -const m = re.exec("date: 2024-03"); -console.log("Test 3: Named groups"); -console.log(" indices:", JSON.stringify(m.indices)); -console.log(" indices.groups:", JSON.stringify(m.indices.groups)); -console.log(" Expected groups: {year:[6,10], month:[11,13]}"); -EOF - -# Test 4: Unmatched optional group -cat > "$TEST_DIR/test4.ts" << 'EOF' -const re = /(\d+)(\.(\d+))?/d; -const m = re.exec("int: 42"); -console.log("Test 4: Unmatched optional group"); -console.log(" indices:", JSON.stringify(m.indices)); -console.log(" indices[2] (unmatched):", m.indices[2]); -console.log(" Expected: undefined"); -EOF - -# Test 5: Without d flag -cat > "$TEST_DIR/test5.ts" << 'EOF' -const re = /test/; -const m = re.exec("test"); -console.log("Test 5: Without d flag"); -console.log(" indices:", m.indices); -console.log(" Expected: undefined"); -console.log(" hasIndices:", re.hasIndices); -console.log(" Expected: false"); -EOF - -echo "=== Test 1: Basic indices ===" -$PERRY "$TEST_DIR/test1.ts" -o "$TEST_DIR/test1" 2>&1 | tail -2 -"$TEST_DIR/test1" -echo "" - -echo "=== Test 2: Capture groups ===" -$PERRY "$TEST_DIR/test2.ts" -o "$TEST_DIR/test2" 2>&1 | tail -2 -"$TEST_DIR/test2" -echo "" - -echo "=== Test 3: Named groups ===" -$PERRY "$TEST_DIR/test3.ts" -o "$TEST_DIR/test3" 2>&1 | tail -2 -"$TEST_DIR/test3" -echo "" - -echo "=== Test 4: Unmatched optional group ===" -$PERRY "$TEST_DIR/test4.ts" -o "$TEST_DIR/test4" 2>&1 | tail -2 -"$TEST_DIR/test4" -echo "" - -echo "=== Test 5: Without d flag ===" -$PERRY "$TEST_DIR/test5.ts" -o "$TEST_DIR/test5" 2>&1 | tail -2 -"$TEST_DIR/test5" -echo "" - -echo "=== All tests completed! ===" diff --git a/tests/test_regex_replace_fn_lookahead.sh b/tests/test_regex_replace_fn_lookahead.sh deleted file mode 100755 index f2bac61003..0000000000 --- a/tests/test_regex_replace_fn_lookahead.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# `String.prototype.replace(re, fn)` must fire the callback even when the regex -# needs lookahead/backreferences (which the `regex` crate can't compile, so the -# pattern is stashed in the fancy-regex FANCY_CACHE behind a never-match -# placeholder). The callback-replace path had no fancy-regex fallback, so the -# callback never fired and the input came back unchanged — get-intrinsic's -# `stringToPath` (`'%String.prototype.indexOf%'.replace(/…(?=…)…/g, fn)`) -# returned an empty parts list, breaking the whole call-bound→qs→Stripe chain. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -// Lookahead alternative — needs fancy-regex. -function run(re: RegExp, s: string): string[] { - const out: string[] = []; - s.replace(re, (m: string) => { out.push(m); return m; }); - return out; -} -// `[a-z]+ | (?=\.)`: first match "ab", a zero-width lookahead at '.', "cd". -const a = run(/[a-z]+|(?=\.)/g, "ab.cd"); -if (JSON.stringify(a) !== '["ab","cd"]') throw new Error("A: " + JSON.stringify(a)); -// Pure zero-width lookahead repeated — must not infinite-loop. -const d = run(/(?=\.)/g, "a.b.c"); -if (JSON.stringify(d) !== '["",""]') throw new Error("D: " + JSON.stringify(d)); -// get-intrinsic's stringToPath regex. -const rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g; -const result: any[] = []; -"%String.prototype.indexOf%".replace(rePropName, function (m: any, num: any, q: any, sub: any) { - result[result.length] = q ? sub : (num || m); - return m; -} as any); -if (JSON.stringify(result) !== '["String","prototype","indexOf"]') { - throw new Error("rePropName: " + JSON.stringify(result)); -} -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: regex replace callback with lookahead (fancy-regex fallback)" diff --git a/tests/test_request_subclass_body.sh b/tests/test_request_subclass_body.sh deleted file mode 100755 index 157512f1b5..0000000000 --- a/tests/test_request_subclass_body.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# Regression: subclassing the native global `Request` (or `Response`) must -# produce instances that still expose the body-reading methods -# (`text`/`json`/`arrayBuffer`/`blob`/`formData`) and the inherited property -# getters (`url`/`method`/...). -# -# Bug: a Web-Fetch Request in Perry is a native registry handle (a small id -# NaN-boxed as a pointer), not a heap JS object — its methods are resolved by -# native handle dispatch, not via a JS prototype chain. `class X extends -# Request {}` produced a plain JS object with NO link to a native handle, so -# `sub.text` was `undefined` and `sub instanceof Request` was `false`. This -# broke `@hono/node-server`, which does `class Request extends GlobalRequest`, -# making every `c.req.text()`/`c.req.json()` throw on POST/PUT routes. -# -# Fix: `super(...)` on Request/Response allocates the underlying native handle -# and stashes its id on `this` under `__perry_fetch_handle__`. Inherited body -# methods, property getters, and `instanceof` are forwarded to that handle at -# runtime. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -// No-own-constructor subclass (default ctor forwards args to super()). -const Sub = class extends Request {}; -// Explicit-constructor subclass calling super() — mirrors @hono/node-server. -class Req2 extends Request { - tag: string; - constructor(input: string, init?: RequestInit) { - super(input, init); - this.tag = "req2"; - } -} - -async function main() { - const sub = new Sub("http://x/y", { method: "POST", body: "hello" }); - console.log("sub.text type:", typeof sub.text); - console.log("sub instanceof Request:", sub instanceof Request); - console.log("sub.text body:", await sub.text()); - - const r2 = new Req2("http://x/z", { method: "POST", body: '{"n":42}' }); - console.log("r2.tag:", r2.tag); - console.log("r2 instanceof Request:", r2 instanceof Request); - console.log("r2.method:", r2.method); - const j = await r2.json(); - console.log("r2.json.n:", j.n); -} -main(); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="sub.text type: function -sub instanceof Request: true -sub.text body: hello -r2.tag: req2 -r2 instanceof Request: true -r2.method: POST -r2.json.n: 42" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: Request subclass body methods regressed" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_request_subclass_stream_body.sh b/tests/test_request_subclass_stream_body.sh deleted file mode 100755 index 758eb57e74..0000000000 --- a/tests/test_request_subclass_stream_body.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -# Regression: a `class X extends Request` instance must (1) expose the body -# methods (`text`/`json`/`formData`/...) as READABLE callable values — both -# `r.text` and the COMPUTED `r["text"]` (@hono/node-server reads the body via -# `this[getRequestCache]()[k]()`, a computed member call), and (2) read a -# `ReadableStream` body, not just a string body. -# -# Bug (this PR): the property READ of a body method on a fetch-subclass forwarded -# to the native handle id as if it were an ObjectHeader and returned `undefined`, -# so `r["text"]` was not a function ("text is not a function"). And a -# `ReadableStream` request body was coerced as a string — the stream HANDLE got -# stringified to its numeric id, so `await r.text()` resolved to a bogus number. -# -# Fix: body-method reads on a fetch-subclass return a bound method that -# re-dispatches through the native handle; the Request thunk drains a -# ReadableStream body (buffered chunks) into bytes via the registered -# `js_response_body_init_ptr`. - -set -e -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << "EOF" -const GlobalRequest = (globalThis as any).Request; -class Req extends GlobalRequest { - constructor(input: any, init?: any) { super(input, init); } -} -async function main() { - // String body via literal + computed body-method read. - const r = new Req("http://x/y", { method: "POST", body: "HI" }); - console.log("typeof r.text:", typeof r.text); - const k = "text"; - console.log("typeof r[k]:", typeof (r as any)[k]); - console.log("literal text:", await r.text()); - // ReadableStream body (single synchronously-enqueued chunk) — drained. - const r2 = new Req("http://x/z", { - method: "POST", - body: new ReadableStream({ - start(c: any) { c.enqueue(new TextEncoder().encode("STREAMED")); c.close(); }, - }), - }); - const fn = (r2 as any)["text"]; - console.log("computed stream text:", await fn.call(r2)); -} -main(); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts -o test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -EXPECTED="typeof r.text: function -typeof r[k]: function -literal text: HI -computed stream text: STREAMED" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi -echo "FAIL: Request subclass stream/computed body methods regressed" -echo "Expected:"; echo "$EXPECTED" -echo ""; echo "Got:"; echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_stream_prototype_eventemitter_borrow.sh b/tests/test_stream_prototype_eventemitter_borrow.sh deleted file mode 100755 index 5ce8a8f99b..0000000000 --- a/tests/test_stream_prototype_eventemitter_borrow.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -# Regression: `Stream.prototype..call(this, …)` borrow. -# -# readable-stream's `Readable.prototype.on` is literally: -# Readable.prototype.on = function (ev, fn) { -# var res = Stream.prototype.on.call(this, ev, fn); // <-- line 745 -# ... -# }; -# where `Stream = require('stream')` (in Node `require('stream')` IS the legacy -# `Stream` constructor, so `Stream.prototype.on` is EventEmitter.prototype.on). -# -# Perry models `require('stream')` as a namespace OBJECT, so both -# `require('stream').prototype` and `require('stream').Stream.prototype` lacked -# the EventEmitter methods — `Stream.prototype.on` was `undefined` and the -# borrow threw "Function.prototype.call was called on a value that is not a -# function". This wall blocked winston (`class Logger extends Transform`). -# -# Fix: -# - the legacy `Stream.prototype` (and Readable/Writable/Duplex/Transform/ -# PassThrough prototypes) now expose the EventEmitter methods as -# receiver-from-`this` values, and -# - `require('stream').prototype` resolves to that same legacy prototype. -# -# This test borrows `on`/`emit` off both forms and verifies they register and -# dispatch the listener against the call-site `this`. -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -SRC="$TMPDIR/stream_proto_ee_borrow.ts" -BIN="$TMPDIR/stream_proto_ee_borrow" - -cat >"$SRC" <<'TS' -const Stream: any = require("stream"); - -let failures = 0; - -// Form A: the namespace itself (`require('stream').prototype`), the exact shape -// readable-stream borrows from. -if (typeof Stream.prototype !== "object" || typeof Stream.prototype.on !== "function") { - console.log("FAIL: require('stream').prototype.on is not a function"); - failures = failures + 1; -} else { - const a: any = {}; - let gotA: any = null; - Stream.prototype.on.call(a, "data", (x: any) => { gotA = x; }); - Stream.prototype.emit.call(a, "data", 11); - if (gotA !== 11) { console.log("FAIL: namespace borrow lost the listener"); failures = failures + 1; } -} - -// Form B: the `Stream` constructor export (`require('stream').Stream`). -const SS: any = Stream.Stream; -if (typeof SS.prototype.on !== "function" || typeof SS.prototype.removeListener !== "function") { - console.log("FAIL: Stream.prototype EE methods missing"); - failures = failures + 1; -} else { - const b: any = {}; - let count = 0; - const fn = () => { count = count + 1; }; - SS.prototype.on.call(b, "x", fn); - SS.prototype.emit.call(b, "x"); - SS.prototype.removeListener.call(b, "x", fn); - SS.prototype.emit.call(b, "x"); - if (count !== 1) { console.log("FAIL: removeListener borrow did not unregister (count=" + count + ")"); failures = failures + 1; } -} - -if (failures !== 0) { - throw new Error("Stream.prototype EventEmitter borrow regression failed"); -} -console.log("stream proto ee borrow ok"); -TS - -"$PERRY" compile --no-cache --no-auto-optimize "$SRC" -o "$BIN" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 - exit 1 -} - -"$BIN" >"$TMPDIR/run.log" 2>&1 || { - echo "FAIL: program failed" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -} - -if ! grep -q "stream proto ee borrow ok" "$TMPDIR/run.log"; then - echo "FAIL: expected success marker" - sed 's/^/ /' "$TMPDIR/run.log" | tail -80 - exit 1 -fi - -echo "PASS: stream prototype EventEmitter borrow" diff --git a/tests/test_string_append_surrogate_repair.sh b/tests/test_string_append_surrogate_repair.sh deleted file mode 100755 index 58f6f8e0c6..0000000000 --- a/tests/test_string_append_surrogate_repair.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -# Regression: `+=` string append must re-pair a split UTF-16 surrogate the same -# way expression concat and Node do. Found while testing pi #6728: pi's -# `visibleWidth` strips ANSI by rebuilding a string one code unit at a time -# (`stripped += clean[i]`), which splits an emoji's surrogate pair across two -# `+=` appends. perry's append path used to copy the two lone 3-byte WTF-8 -# surrogates verbatim instead of coalescing them into the astral char's 4-byte -# UTF-8 — so `[...s].length`/`codePointAt` disagreed with Node, and pi's TUI -# width invariant aborted on any emoji in a colored line. -# -# This is a differential test: perry's output must be byte-identical to Node's. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi -if ! command -v node >/dev/null 2>&1; then - echo "SKIP: node not found (differential test needs node)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -COMPILE_ENV=() -if [ -f "$SCRIPT_DIR/../target/debug/libperry_runtime.a" ] || [ -f "$SCRIPT_DIR/../target/release/libperry_runtime.a" ]; then - COMPILE_ENV=(env PERRY_NO_AUTO_OPTIMIZE=1) -fi - -cat > "$TMPDIR/main.ts" << 'EOF' -function show(label: string, s: string): void { - console.log(label + " len=" + s.length + " cp=" + [...s].length + " cp0=" + s.codePointAt(0) + " eq="); -} -const e = "a👋b"; // real astral char (UTF-8 in source) -const hi = e[1], lo = e[2]; // lone high + low surrogate from indexing - -// 1. The bug: incremental += of the two halves must re-pair. -let Y = ""; Y += hi; Y += lo; -show("pluseq", Y); -console.log("pluseq-eq", Y === "👋", (hi + lo) === Y); - -// 2. pi's ANSI-strip pattern: rebuild a string one code unit at a time. -let out = ""; -for (let i = 0; i < e.length; i++) out += e[i]; -console.log("rebuild", out === e, [...out].length); - -// 3. Multiple emoji, some with a leading ASCII run (exercises both branches). -const t = "x😀y👋z🎉w"; -let r = ""; -for (let i = 0; i < t.length; i++) r += t[i]; -console.log("multi", r === t, [...r].length, r.codePointAt(1)); - -// 4. A genuinely lone surrogate must STAY lone (no false merge). -let L = ""; L += hi; L += "Z"; -console.log("lone", [...L].length, L.codePointAt(0), L.length); - -// 5. ASCII fast path is unaffected (and stays correct at scale). -let A = ""; -for (let i = 0; i < 500; i++) A += "aé"; // 1 ascii + 1 two-byte utf8 -console.log("bulk", A.length, [...A].length); - -// 6. Emoji halves split by an unrelated append in between (no false pair). -let M = ""; M += hi; M += "-"; M += lo; -console.log("split", [...M].length, M.codePointAt(0)); -EOF - -cd "$TMPDIR" -"${COMPILE_ENV[@]}" "$PERRY" compile main.ts --output test_bin --no-cache >/dev/null 2>&1 -PERRY_OUT=$(./test_bin 2>&1) -NODE_OUT=$(node main.ts 2>&1) - -if [ "$PERRY_OUT" = "$NODE_OUT" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: perry output diverged from node (surrogate re-pairing on +=)" -echo "--- node ---"; echo "$NODE_OUT" -echo "--- perry ---"; echo "$PERRY_OUT" -diff <(echo "$NODE_OUT") <(echo "$PERRY_OUT") || true -exit 1 diff --git a/tests/test_subclass_builtin_field_init.sh b/tests/test_subclass_builtin_field_init.sh deleted file mode 100755 index c9e2c6b824..0000000000 --- a/tests/test_subclass_builtin_field_init.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A no-own-constructor subclass of a BUILT-IN (Error, etc.) must still run its -# OWN field initializers. The implicit ctor is -# `constructor(...args){ super(...args); }`. -# -# The field-init chain only contains USER classes, so a built-in parent is not -# in it: `class A extends Error {}` has chain `["A"]` (length 1). The no-own-ctor -# path applied `FieldInitMode::AfterRoot`, which keeps `chain[1..]` — EMPTY for a -# length-1 chain — so A's own field initializers never ran. Fields read the -# raw-0 slot; a later `this.arr.includes(x)` on an unset array field then threw -# `Cannot read properties of undefined (reading 'includes')`. (A two-level -# `class B extends A extends Error` worked because its chain is `["A","B"]`.) -# -# Fix: when the chain has no user root to skip (built-in / imported parent), -# `AfterRoot` applies the leaf's own initializers. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -class A extends Error { - pubv = 42; // public field initializer - #priv = 7; // private field initializer - arr = [1, 2, 3]; // reference-typed field - readPub() { return this.pubv; } - readPriv() { return this.#priv; } - readArr() { return this.arr; } -} -const a = new A(); -if (a.readPub() !== 42) throw new Error("pubv: " + a.readPub()); -if (a.readPriv() !== 7) throw new Error("priv: " + a.readPriv()); -if (!a.readArr().includes(2)) throw new Error("arr uninitialized: " + JSON.stringify(a.readArr())); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: subclass-of-builtin own field initializers run" diff --git a/tests/test_subclass_own_field_init_after_super.sh b/tests/test_subclass_own_field_init_after_super.sh deleted file mode 100755 index 61d7ecf07e..0000000000 --- a/tests/test_subclass_own_field_init_after_super.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A subclass with NO own constructor, extending a parent that HAS one, must still -# run its OWN field initializers (the implicit default ctor is -# `constructor(...args){ super(...args); }`). The inline -# construction path applied `UpToInclusive(inherited_ctor_class)` up front — which -# keeps `chain[0..=idx(inherited)]` and EXCLUDES the leaf — and never applied the -# leaf's fields after super(). So a subclass's own `field = ` never ran; the -# field read the raw-0 slot. -# -# zod hit this exactly: `class ZodObject extends ZodType { private _cached = null }` -# left `_cached` at 0, so `_getCached()`'s `this._cached !== null` was true -# (0 !== null) and returned 0; `_parse` destructured `{ keys }` off 0 and every -# `z.object({...}).parse()` silently dropped all fields. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -abstract class Base { - _def: any; - constructor(def: any) { this._def = def; } - abstract _p(): any; -} -class Leaf extends Base { // no own ctor; own field initializers - _cached: any = null; - _count = 7; - _list: number[] = [1, 2]; - _p() { return 1; } - static create = (): Leaf => new Leaf({ k: 1 }); -} - -const o: any = Leaf.create(); -if (o._cached !== null) throw new Error("_cached: " + o._cached); // not raw-0 -if (o._cached === 0) throw new Error("_cached is 0"); -if (o._count !== 7) throw new Error("_count: " + o._count); -if (JSON.stringify(o._list) !== "[1,2]") throw new Error("_list: " + JSON.stringify(o._list)); -if (JSON.stringify(o._def) !== '{"k":1}') throw new Error("_def (super): " + JSON.stringify(o._def)); -console.log("OK"); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -if ! grep -q "^OK$" <<<"$OUT"; then echo "FAIL: expected OK, got:"; echo "$OUT"; exit 1; fi -echo "PASS: subclass own field init after super" diff --git a/tests/test_textencoder_hoisted_function_decl.sh b/tests/test_textencoder_hoisted_function_decl.sh deleted file mode 100755 index f34cfe68e2..0000000000 --- a/tests/test_textencoder_hoisted_function_decl.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "Perry binary not found; run cargo build -p perry first" >&2 - exit 1 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat > "$TMPDIR/main.js" <<'JS' -var util = require('util') - -function encodeLength(content) { - content = textEncoder.encode(content) - return content.byteLength -} - -var textEncoder = new util.TextEncoder() - -var nestedLen = (function () { - function encodeLengthNested(content) { - content = nestedTextEncoder.encode(content) - return content.byteLength - } - - var nestedTextEncoder = new util.TextEncoder() - return encodeLengthNested('world') -})() - -console.log('len=' + encodeLength('hello') + ',nested=' + nestedLen) -JS - -"$PERRY" compile --no-cache "$TMPDIR/main.js" -o "$TMPDIR/out" >"$TMPDIR/compile.log" 2>&1 || { - cat "$TMPDIR/compile.log" >&2 - exit 1 -} - -output="$($TMPDIR/out)" -if [[ "$output" != "len=5,nested=5" ]]; then - echo "Unexpected output: $output" >&2 - exit 1 -fi diff --git a/tests/test_try_block_no_leak_on_return.sh b/tests/test_try_block_no_leak_on_return.sh deleted file mode 100755 index fe91441931..0000000000 --- a/tests/test_try_block_no_leak_on_return.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# Regression: `return` inside a `try { ... }` body skipped `js_try_end`, -# leaking one slot in the runtime's setjmp jump-buffer table per call. -# The cap is 128 — programs hitting the early-return-in-try path enough -# times panic with "Try block nesting too deep". -# -# Fix: `Stmt::Return` lowering now emits `ctx.try_depth` `js_try_end` -# calls before the `ret` so the runtime's TRY_DEPTH counter stays -# balanced. -# -# This test exercises the specific shape (try-body containing an -# early-return path) hundreds of times in a tight loop. Pre-fix this -# would panic before iteration 128; post-fix it runs to completion. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -// Each call to `step` enters a try, takes the early-return branch, -// and leaks one TRY_DEPTH slot pre-fix. With MAX_TRY_DEPTH = 128, -// the 128th call panics. Run 500 iterations so we'd be deep into -// the panic territory if the leak still existed. -function step(i: number): number { - try { - if (i >= 0) return i; // early return inside try body - return -1; - } catch (e: any) { - return -2; - } -} - -let sum = 0; -for (let i = 0; i < 500; i++) { - sum += step(i); -} -console.log("sum=" + sum); -console.log("done"); -EOF - -cd "$TMPDIR" -"$PERRY" compile main.ts --output test_bin >/dev/null 2>&1 -RUN_OUTPUT=$(./test_bin 2>&1) - -# Sum of 0..499 = 124750 -EXPECTED="sum=124750 -done" - -if [ "$RUN_OUTPUT" = "$EXPECTED" ]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: try-block depth leaked or wrong sum" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_try_catch_across_await.sh b/tests/test_try_catch_across_await.sh deleted file mode 100755 index 39335433b4..0000000000 --- a/tests/test_try_catch_across_await.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -# Regression: a `try/catch` (or `try/finally`) that SPANS an `await` leaked one -# runtime exception-handler slot (`try_depth`) every time it executed. The async -# state machine lowers `await` to a suspend that `break`s out of the dispatch -# loop's real `try { ... }`; that break skipped the matching `js_try_end`, so -# `try_depth` climbed by one per awaited try/catch and the runtime aborted with -# "Try block nesting too deep" at MAX_TRY_DEPTH = 128. This killed any -# long-running async server whose handlers `try { await ... } catch`. -# -# Fix: codegen now emits a compensating `js_try_end` for every open `try` frame -# a `break`/`continue`/labeled-break/labeled-continue jumps OUT of (the same -# balancing `Stmt::Return` already did), so `try_depth` nets to zero across the -# suspend boundary while keeping the catch/finally reachable. -# -# Coverage: -# (a) long NON-throwing awaited try/catch loop (the leak path — no throw needed) -# (b) throw-after-await still caught by the enclosing try -# (c) finally still runs across an await -# (d) nested awaited try/catch - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY:-${PERRY_BIN:-$REPO_ROOT/target/release/perry}}" - -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build --release -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat > "$TMPDIR/main.ts" << 'EOF' -// (a) Long non-throwing awaited try/catch loop. Pre-fix this leaks one -// try_depth slot per iteration and aborts before iteration 128. -async function noThrow(i: number): Promise { - try { await Promise.resolve(i); return i; } catch { return -1; } -} - -// (b) A throw AFTER an await inside the try must still reach this try's catch. -async function withThrow(i: number): Promise { - try { await Promise.reject(new Error("boom")); return i; } catch { return -1; } -} - -// (c) finally must run across an await (and on every iteration, no leak). -let finallyRuns = 0; -async function withFinally(i: number): Promise { - try { await Promise.resolve(i); return i; } finally { finallyRuns++; } -} - -// (d) Nested awaited try/catch: inner throw-after-await caught by inner catch, -// outer try/catch also spans an await. Run many times to exercise the leak. -async function nested(i: number): Promise { - try { - await Promise.resolve(i); - try { - await Promise.reject(new Error("inner")); - return 999; - } catch { - return i; - } - } catch { - return -2; - } -} - -async function main(): Promise { - let sumNoThrow = 0; - for (let i = 0; i < 400; i++) { sumNoThrow += await noThrow(i); } - console.log("noThrow sum=" + sumNoThrow); - - let sumThrow = 0; - for (let i = 0; i < 400; i++) { sumThrow += await withThrow(i); } - console.log("withThrow sum=" + sumThrow); - - let sumFinally = 0; - for (let i = 0; i < 400; i++) { sumFinally += await withFinally(i); } - console.log("withFinally sum=" + sumFinally + " finallyRuns=" + finallyRuns); - - let sumNested = 0; - for (let i = 0; i < 400; i++) { sumNested += await nested(i); } - console.log("nested sum=" + sumNested); - - console.log("DONE"); -} -main().catch((e) => console.log("ERR: " + String((e as any)?.message ?? e))); -EOF - -"$PERRY" compile "$TMPDIR/main.ts" --output "$TMPDIR/test_bin" >"$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: compile failed" - sed 's/^/ /' "$TMPDIR/compile.log" | tail -60 - exit 1 -} - -RUN_OUTPUT="$("$TMPDIR/test_bin" 2>&1)" - -# noThrow returns i for each i in 0..399 → sum 0..399 = 79800 -# withThrow returns -1 for each of 400 → -400 -# withFinally returns i for each i in 0..399 → 79800, finally runs 400 times -# nested: inner throw-after-await is caught by inner catch which returns i → sum 79800 -EXPECTED="noThrow sum=79800 -withThrow sum=-400 -withFinally sum=79800 finallyRuns=400 -nested sum=79800 -DONE" - -if [[ "$RUN_OUTPUT" == "$EXPECTED" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: try/catch-across-await output changed (leak or wrong semantics)" -echo "Expected:" -echo "$EXPECTED" -echo -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_user_method_named_sort.sh b/tests/test_user_method_named_sort.sh deleted file mode 100755 index aab847410a..0000000000 --- a/tests/test_user_method_named_sort.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# A user-defined method named `sort` on a class instance — or a function -# re-exported as `sort` from an imported module (semver: -# `sort = (list) => list.sort(cmp)` called as `semver.sort(list)`) — must NOT -# be folded into the `Expr::ArraySort` array intrinsic. The fold mis-routed the -# single `list` argument into the comparator slot, so the runtime comparator -# validator saw the array (not a function) and threw "The comparison function -# must be either a function or undefined: ...". The HIR sort arms in -# `array_only_methods.rs` / `imported_array_methods.rs` now require the -# receiver to be a known array (not a class instance / imported binding) before -# folding, mirroring the existing `map`/`filter`/`join` guards. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then PERRY="$REPO_ROOT/target/debug/perry"; fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/f.ts" <<'TS' -// A wrapper class whose `sort(list)` method internally calls Array.sort with -// a comparator — exactly semver's re-exported `sort`. -class Sorter { - sort(list: any[]) { - return list.sort((a: any, b: any) => (a < b ? -1 : a > b ? 1 : 0)); - } -} -const s = new Sorter(); -console.log(JSON.stringify(s.sort(["1.2.0", "1.0.1", "1.0.0", "2.0.0"]))); -TS - -OUT="$("$PERRY" run "$TMPDIR/f.ts" 2>&1)" || { echo "FAIL: perry run errored"; echo "$OUT"; exit 1; } -EXPECT='["1.0.0","1.0.1","1.2.0","2.0.0"]' -if ! grep -qF "$EXPECT" <<<"$OUT"; then - echo "FAIL: expected $EXPECT, got:"; echo "$OUT"; exit 1 -fi -echo "PASS: user-method-named-sort is not folded to the array intrinsic" diff --git a/tests/test_webassembly_graceful_fail.sh b/tests/test_webassembly_graceful_fail.sh deleted file mode 100755 index b6455c8d50..0000000000 --- a/tests/test_webassembly_graceful_fail.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash -# #6558 — graceful-fail baseline for the WebAssembly global (perry-only -# assertions; the node-parity side lives in -# test-files/test_gap_6558_webassembly_graceful_fail.ts and -# test-parity/node-suite/globals/webassembly-graceful-degradation.ts). -# -# What node CANNOT parity-check: in the default perry build (no wasmi host -# linked), even a perfectly VALID wasm module must be answered honestly — -# `validate` → false, `compile` → rejection whose CompileError message names -# the API and points at issue #6558 — while the program continues and exits 0. -# -# Uses the ALIASED namespace spelling: the literal `WebAssembly.compile(...)` -# form lowers to the wasmi host intrinsics (issue #76) and auto-links -# libperry_wasm_host.a, which is exactly the path this test must avoid. - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PERRY="$SCRIPT_DIR/../target/release/perry" -[ ! -f "$PERRY" ] && PERRY="$SCRIPT_DIR/../target/debug/perry" -if [ ! -f "$PERRY" ]; then - echo "SKIP: perry binary not found (build with cargo build --release)" - exit 0 -fi - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -cat > "$TMPDIR/graceful.ts" << 'EOF' -const WA: any = (globalThis as any).WebAssembly; - -// The canonical valid i32 add module (~41 bytes): -// (module (func (export "add") (param i32 i32) (result i32) -// local.get 0 local.get 1 i32.add)) -const validAdd = new Uint8Array([ - 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, - 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, - 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, - 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, - 0x0b, -]); - -// Honest feature detection: a VALID module is still not runnable here. -console.log("validate valid:", WA.validate(validAdd)); - -// compile(valid) rejects with a CompileError pointing at #6558. -try { - await WA.compile(validAdd); - console.log("compile: resolved"); -} catch (e: any) { - console.log("compile rejected:", e instanceof WA.CompileError, e.name); - console.log("mentions api:", e.message.includes("WebAssembly.compile")); - console.log("mentions issue:", e.message.includes("6558")); -} - -// new Module(valid) throws a CompileError pointing at #6558. -try { - new WA.Module(validAdd); - console.log("Module: constructed"); -} catch (e: any) { - console.log("Module threw:", e instanceof WA.CompileError, e.name); - console.log("Module mentions issue:", e.message.includes("6558")); -} - -// instantiate(valid) rejects; the loader pattern degrades to null and the -// program CONTINUES (photon-node shape). -async function lazyLoad(bytes: Uint8Array): Promise { - try { - return await WA.instantiate(bytes); - } catch { - return null; - } -} -const loaded = await lazyLoad(validAdd); -console.log("loader result:", loaded === null ? "degraded" : "loaded"); -console.log("program continues"); -EOF - -OUT="$TMPDIR/out.txt" -"$PERRY" compile "$TMPDIR/graceful.ts" -o "$TMPDIR/graceful" > "$TMPDIR/compile.log" 2>&1 || { - echo "FAIL: perry compile failed" - tail -20 "$TMPDIR/compile.log" - exit 1 -} -"$TMPDIR/graceful" > "$OUT" 2>&1 || { - echo "FAIL: compiled binary exited non-zero" - cat "$OUT" - exit 1 -} - -expect() { - if ! grep -qF "$1" "$OUT"; then - echo "FAIL: missing expected line: $1" - echo "--- actual output ---" - cat "$OUT" - exit 1 - fi -} - -expect "validate valid: false" -expect "compile rejected: true CompileError" -expect "mentions api: true" -expect "mentions issue: true" -expect "Module threw: true CompileError" -expect "Module mentions issue: true" -expect "loader result: degraded" -expect "program continues" - -echo "PASS: WebAssembly namespace degrades gracefully (#6558 baseline)" diff --git a/tests/test_xmod_empty_imported_derived_arg_forward.sh b/tests/test_xmod_empty_imported_derived_arg_forward.sh deleted file mode 100755 index cc864f706a..0000000000 --- a/tests/test_xmod_empty_imported_derived_arg_forward.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -# Regression: an imported derived class with no own constructor and no fields -# must not stop constructor dispatch. Args should still reach the first -# effectful imported ancestor constructor. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi -if [[ "$PERRY" != /* ]]; then - PERRY="$(cd "$(dirname "$PERRY")" && pwd)/$(basename "$PERRY")" -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat > "$TMPDIR/root.ts" <<'TS' -export class EffectfulRoot { - constructor(value: any) { - (this as any).forwarded = value; - } -} -TS - -cat > "$TMPDIR/mid.ts" <<'TS' -import { EffectfulRoot } from "./root.ts"; - -export class EmptyMid extends EffectfulRoot {} -TS - -cat > "$TMPDIR/main.ts" <<'TS' -import { EmptyMid } from "./mid.ts"; - -const direct: any = new EmptyMid("direct"); -console.log("direct instanceof mid", direct instanceof EmptyMid); -console.log("direct forwarded", direct.forwarded); - -class ExplicitLeaf extends EmptyMid { - constructor(value: any) { - super(value); - (this as any).leafReady = "explicit"; - } -} - -const explicit: any = new ExplicitLeaf("explicit"); -console.log("explicit instanceof mid", explicit instanceof EmptyMid); -console.log("explicit forwarded", explicit.forwarded); -console.log("explicit ready", explicit.leafReady); - -class DefaultLeaf extends EmptyMid {} - -const defaultLeaf: any = new DefaultLeaf("default"); -console.log("default instanceof mid", defaultLeaf instanceof EmptyMid); -console.log("default forwarded", defaultLeaf.forwarded); -TS - -cd "$TMPDIR" -"$PERRY" compile --no-cache --no-auto-optimize main.ts --output test_bin >/dev/null -RUN_OUTPUT="$(./test_bin 2>&1)" - -EXPECTED="direct instanceof mid true -direct forwarded direct -explicit instanceof mid true -explicit forwarded explicit -explicit ready explicit -default instanceof mid true -default forwarded default" - -if [[ "$RUN_OUTPUT" == "$EXPECTED" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: empty imported derived class did not forward args to effectful ancestor" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_xmod_imported_multilevel_ctor_state.sh b/tests/test_xmod_imported_multilevel_ctor_state.sh deleted file mode 100755 index c2edef1099..0000000000 --- a/tests/test_xmod_imported_multilevel_ctor_state.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Regression: imported multi-level native classes must replay inherited -# constructor state all the way up the chain. This mirrors the Three.js shape -# PerspectiveCamera -> Camera -> Object3D, where Object3D assigns object-valued -# instance state and Camera redeclares some inherited fields with `declare`. - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi -if [[ "$PERRY" != /* ]]; then - PERRY="$(cd "$(dirname "$PERRY")" && pwd)/$(basename "$PERRY")" -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat >"$TMPDIR/base_object.ts" <<'TS' -export class MatrixLike { - label: string; - - constructor(label: string) { - this.label = label; - } - - decompose() { - return "decompose:" + this.label; - } -} - -export class BaseObject { - constructor() { - Object.defineProperties(this, { - hiddenMarker: { value: "hidden", configurable: true } - }); - (this as any).plainBase = "base"; - (this as any).matrixWorld = new MatrixLike("world"); - } - - updateMatrixWorld(force?: boolean) { - return (this as any).matrixWorld.decompose() + ":" + String(force); - } -} -TS - -cat >"$TMPDIR/camera_base.ts" <<'TS' -import { BaseObject, MatrixLike } from "./base_object.ts"; - -export class CameraBase extends BaseObject { - declare matrixWorld: MatrixLike; - declare matrixWorldInverse: MatrixLike; - - constructor() { - super(); - (this as any).matrixWorldInverse = new MatrixLike("inverse"); - (this as any).cameraReady = true; - } -} -TS - -cat >"$TMPDIR/perspective_like.ts" <<'TS' -import { CameraBase } from "./camera_base.ts"; -import { MatrixLike } from "./base_object.ts"; - -export class PerspectiveLike extends CameraBase { - constructor(fov: number, aspect: number, near: number, far: number) { - super(); - (this as any).isPerspectiveLike = true; - (this as any).projectionMatrix = new MatrixLike( - "projection:" + fov + ":" + aspect + ":" + near + ":" + far - ); - } -} -TS - -cat >"$TMPDIR/main.ts" <<'TS' -import { BaseObject } from "./base_object.ts"; -import { CameraBase } from "./camera_base.ts"; -import { PerspectiveLike } from "./perspective_like.ts"; - -const camera: any = new PerspectiveLike(50, 1, 0.1, 100); -console.log("isPerspective", camera.isPerspectiveLike === true); -console.log("instanceofCamera", camera instanceof CameraBase); -console.log("instanceofBase", camera instanceof BaseObject); -console.log("projection", camera.projectionMatrix.decompose()); -console.log("plainBase", camera.plainBase); -console.log("matrixWorld", camera.matrixWorld.decompose()); -console.log("update", camera.updateMatrixWorld(false)); -console.log("inverse", camera.matrixWorldInverse.decompose()); -TS - -cd "$TMPDIR" -"$PERRY" compile --no-cache --no-auto-optimize main.ts --output test_bin >/dev/null -RUN_OUTPUT="$(./test_bin 2>&1)" - -EXPECTED="isPerspective true -instanceofCamera true -instanceofBase true -projection decompose:projection:50:1:0.1:100 -plainBase base -matrixWorld decompose:world -update decompose:world:false -inverse decompose:inverse" - -if [[ "$RUN_OUTPUT" == "$EXPECTED" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: imported multi-level inherited constructor state was not preserved" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1 diff --git a/tests/test_xmod_imported_zero_arg_super.sh b/tests/test_xmod_imported_zero_arg_super.sh deleted file mode 100755 index e9d3bd71c9..0000000000 --- a/tests/test_xmod_imported_zero_arg_super.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# Regression: an explicit zero-argument constructor on an imported superclass -# must run when a local subclass calls super(). - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" -if [[ ! -x "$PERRY" ]]; then - PERRY="$REPO_ROOT/target/debug/perry" -fi -if [[ ! -x "$PERRY" ]]; then - echo "SKIP: perry binary not found (build with cargo build -p perry)" - exit 0 -fi -if [[ "$PERRY" != /* ]]; then - PERRY="$(cd "$(dirname "$PERRY")" && pwd)/$(basename "$PERRY")" -fi - -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT - -cat > "$TMPDIR/root.ts" <<'TS' -export class CrossRoot { - constructor() { - (this as any).rootReady = "root"; - } -} -TS - -cat > "$TMPDIR/base.ts" <<'TS' -import { CrossRoot } from "./root.ts"; - -export class CrossBase extends CrossRoot { - constructor() { - super(); - (this as any).baseReady = "base"; - } -} -TS - -cat > "$TMPDIR/main.ts" <<'TS' -import { CrossBase } from "./base.ts"; - -class CrossSub extends CrossBase { - constructor() { - super(); - (this as any).subReady = "sub"; - } -} - -const value: any = new CrossSub(); -console.log("instanceof base", value instanceof CrossBase); -console.log("root", value.rootReady); -console.log("base", value.baseReady); -console.log("sub", value.subReady); -TS - -cd "$TMPDIR" -"$PERRY" compile --no-cache --no-auto-optimize main.ts --output test_bin >/dev/null -RUN_OUTPUT="$(./test_bin 2>&1)" - -EXPECTED="instanceof base true -root root -base base -sub sub" - -if [[ "$RUN_OUTPUT" == "$EXPECTED" ]]; then - echo "PASS" - exit 0 -fi - -echo "FAIL: imported zero-arg superclass constructor was not replayed" -echo "Expected:" -echo "$EXPECTED" -echo "" -echo "Got:" -echo "$RUN_OUTPUT" -exit 1