From 8cebbcbba5d7017f04714cce51ee083cc229e0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 31 Jul 2026 01:41:11 +0200 Subject: [PATCH 1/2] fix(node:test): preserve mock function metadata --- .../perry-runtime/src/node_submodules/test.rs | 57 +++++++++++++++---- .../test_metadata_unit_tests.rs | 26 +++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index 219c40f3d3..296bc50e04 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -567,6 +567,20 @@ fn mock_context_object(id: i64, calls: f64, include_call_tracking: bool) -> f64 boxed_ptr(obj) } +fn mock_function_metadata(original: f64) -> (String, u32) { + if !is_callable_value(original) { + return ("mockFn".to_string(), 0); + } + let closure = raw_ptr_from_value(original) as *const ClosureHeader; + let dynamic_name = crate::closure::closure_get_own_dynamic_prop(closure as usize, "name") + .and_then(value_to_string); + let name = dynamic_name + .or_else(|| unsafe { crate::builtins::function_name_for_ptr((*closure).func_ptr as usize) }) + .unwrap_or_default(); + let length = crate::closure::closure_length(closure).unwrap_or(0); + (name, length) +} + fn create_mock_function(original: f64, implementation: f64, restore: MockRestoreTarget) -> f64 { if !JSValue::from_bits(original.to_bits()).is_undefined() { assert_callable_arg("original", original); @@ -575,29 +589,44 @@ fn create_mock_function(original: f64, implementation: f64, restore: MockRestore assert_callable_arg("implementation", implementation); } + let (name, length) = mock_function_metadata(original); + let scope = crate::gc::RuntimeHandleScope::new(); + let original = scope.root_nanbox_f64(original); + let implementation = scope.root_nanbox_f64(implementation); let id = next_mock_id(); - let calls = boxed_ptr(crate::array::js_array_alloc(0)); - let context = mock_context_object(id, calls, true); - let function = rest_closure_value_with_id(mock_function_invoke as *const u8, 0, id); - let closure_ptr = raw_ptr_from_value(function); + let calls = scope.root_nanbox_f64(boxed_ptr(crate::array::js_array_alloc(0))); + let context = scope.root_nanbox_f64(mock_context_object(id, calls.get_nanbox_f64(), true)); + let function = scope.root_nanbox_f64(rest_closure_value_with_id( + mock_function_invoke as *const u8, + 0, + id, + )); + let closure_ptr = raw_ptr_from_value(function.get_nanbox_f64()); if closure_ptr != 0 { - crate::object::set_bound_native_closure_name(closure_ptr as *mut ClosureHeader, "mockFn"); - crate::closure::closure_set_dynamic_prop(closure_ptr, "mock", context); + crate::object::set_bound_native_closure_name(closure_ptr as *mut ClosureHeader, &name); + let closure_ptr = raw_ptr_from_value(function.get_nanbox_f64()); + crate::object::set_builtin_closure_length(closure_ptr, length); + crate::object::set_builtin_property_attrs( + closure_ptr, + "length".to_string(), + crate::object::PropertyAttrs::new(false, false, true), + ); + crate::closure::closure_set_dynamic_prop(closure_ptr, "mock", context.get_nanbox_f64()); } MOCK_STATES.with(|states| { states.borrow_mut().push(MockState { id, - original, - implementation, + original: original.get_nanbox_f64(), + implementation: implementation.get_nanbox_f64(), once: Vec::new(), - calls, - context, - function, + calls: calls.get_nanbox_f64(), + context: context.get_nanbox_f64(), + function: function.get_nanbox_f64(), restore, }); }); - function + function.get_nanbox_f64() } fn create_restore_context(restore: MockRestoreTarget) -> f64 { @@ -704,6 +733,10 @@ fn record_mock_call(id: i64, args_value: f64, this_value: f64, result: f64, erro }); } +#[cfg(test)] +#[path = "test_metadata_unit_tests.rs"] +mod metadata_tests; + extern "C" fn mock_function_invoke(closure: *const ClosureHeader, rest: f64) -> f64 { let id = closure_id(closure); let args = array_values(rest).unwrap_or_default(); diff --git a/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs b/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs new file mode 100644 index 0000000000..7de39900ed --- /dev/null +++ b/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs @@ -0,0 +1,26 @@ +use super::*; + +extern "C" fn original_two_args(_closure: *const ClosureHeader, _first: f64, _second: f64) -> f64 { + undefined_value() +} + +#[test] +fn mock_function_preserves_original_name_length_and_descriptors() { + let original = make_closure(original_two_args as *const u8, 2, 0); + crate::object::set_bound_native_closure_name(original, "original"); + let original = boxed_ptr(original); + + let mock = create_mock_function(original, original, MockRestoreTarget::None); + let mock_ptr = raw_ptr_from_value(mock); + + let name = crate::closure::closure_get_dynamic_prop(mock_ptr, "name"); + assert_eq!(value_to_string(name).as_deref(), Some("original")); + assert_eq!(crate::object::builtin_closure_length(mock_ptr), Some(2)); + for property in ["name", "length"] { + let attrs = crate::object::get_property_attrs(mock_ptr, property) + .unwrap_or_else(|| panic!("{property} descriptor attrs should be installed")); + assert!(!attrs.writable()); + assert!(!attrs.enumerable()); + assert!(attrs.configurable()); + } +} From 0f8cff8d321143d1c04d42dd5e600bde764af867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 31 Jul 2026 01:41:38 +0200 Subject: [PATCH 2/2] chore: add changelog for PR 7099 --- changelog.d/7099-node-test-mock-name-length.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7099-node-test-mock-name-length.md diff --git a/changelog.d/7099-node-test-mock-name-length.md b/changelog.d/7099-node-test-mock-name-length.md new file mode 100644 index 0000000000..1bfdd78e1a --- /dev/null +++ b/changelog.d/7099-node-test-mock-name-length.md @@ -0,0 +1 @@ +Fixed `node:test` mock functions to preserve the original function's name, length, and property descriptors.