Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7099-node-test-mock-name-length.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `node:test` mock functions to preserve the original function's name, length, and property descriptors.
57 changes: 45 additions & 12 deletions crates/perry-runtime/src/node_submodules/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading