diff --git a/PORTING.md b/PORTING.md index 657cbb3..e6cfa05 100644 --- a/PORTING.md +++ b/PORTING.md @@ -57,7 +57,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_error` | Not ported | Medium | | `test_exception` | Not ported | Medium | | `test_finalizer` | Not ported | Medium | -| `test_function` | Not ported | Medium | +| `test_function` | Ported ✅ | Medium | | `test_general` | Not ported | Hard | | `test_handle_scope` | Ported ✅ | Easy | | `test_instance_data` | Not ported | Medium | diff --git a/tests/js-native-api/test_function/CMakeLists.txt b/tests/js-native-api/test_function/CMakeLists.txt new file mode 100644 index 0000000..81caf27 --- /dev/null +++ b/tests/js-native-api/test_function/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_function test_function.c) diff --git a/tests/js-native-api/test_function/test.js b/tests/js-native-api/test_function/test.js new file mode 100644 index 0000000..15ee2cd --- /dev/null +++ b/tests/js-native-api/test_function/test.js @@ -0,0 +1,47 @@ +"use strict"; +// Flags: --expose-gc + +// Testing api calls for function +const test_function = loadAddon("test_function"); + +function func1() { + return 1; +} +assert.strictEqual(test_function.TestCall(func1), 1); + +function func2() { + console.log("hello world!"); + return null; +} +assert.strictEqual(test_function.TestCall(func2), null); + +function func3(input) { + return input + 1; +} +assert.strictEqual(test_function.TestCall(func3, 1), 2); + +function func4(input) { + return func3(input); +} +assert.strictEqual(test_function.TestCall(func4, 1), 2); + +assert.strictEqual(test_function.TestName.name, "Name"); +assert.strictEqual(test_function.TestNameShort.name, "Name_"); + +let tracked_function = test_function.MakeTrackedFunction(mustCall()); +assert(!!tracked_function); +tracked_function = null; +let gcCount = 0; +await gcUntil("test", () => gcCount++ > 0); + +assert.deepStrictEqual(test_function.TestCreateFunctionParameters(), { + envIsNull: "Invalid argument", + nameIsNull: "napi_ok", + cbIsNull: "Invalid argument", + resultIsNull: "Invalid argument", +}); + +assert.throws(() => test_function.TestBadReturnExceptionPending(), { + code: "throwing exception", + name: "Error", +}); diff --git a/tests/js-native-api/test_function/test_function.c b/tests/js-native-api/test_function/test_function.c new file mode 100644 index 0000000..2b53df6 --- /dev/null +++ b/tests/js-native-api/test_function/test_function.c @@ -0,0 +1,204 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestCreateFunctionParameters(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value result, return_value; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + status = napi_create_function(NULL, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + &result); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + status); + + napi_create_function(env, + NULL, + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + &result); + + add_last_status(env, "nameIsNull", return_value); + + napi_create_function(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + NULL, + NULL, + &result); + + add_last_status(env, "cbIsNull", return_value); + + napi_create_function(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + NULL); + + add_last_status(env, "resultIsNull", return_value); + + return return_value; +} + +static napi_value TestCallFunction(napi_env env, napi_callback_info info) { + size_t argc = 10; + napi_value args[10]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc > 0, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_function, + "Wrong type of arguments. Expects a function as first argument."); + + napi_value* argv = args + 1; + argc = argc - 1; + + napi_value global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value result; + NODE_API_CALL(env, napi_call_function(env, global, args[0], argc, argv, &result)); + + return result; +} + +static napi_value TestFunctionName(napi_env env, napi_callback_info info) { + return NULL; +} + +static void finalize_function(napi_env env, void* data, void* hint) { + napi_ref ref = data; + + // Retrieve the JavaScript undefined value. + napi_value undefined; + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + + // Retrieve the JavaScript function we must call. + napi_value js_function; + NODE_API_CALL_RETURN_VOID(env, napi_get_reference_value(env, ref, &js_function)); + + // Call the JavaScript function to indicate that the generated JavaScript + // function is about to be gc-ed. + NODE_API_CALL_RETURN_VOID(env, + napi_call_function(env, undefined, js_function, 0, NULL, NULL)); + + // Destroy the persistent reference to the function we just called so as to + // properly clean up. + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, ref)); +} + +static napi_value MakeTrackedFunction(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_finalize_cb; + napi_valuetype arg_type; + + // Retrieve and validate from the arguments the function we will use to + // indicate to JavaScript that the function we are about to create is about to + // be gc-ed. + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, &js_finalize_cb, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + NODE_API_CALL(env, napi_typeof(env, js_finalize_cb, &arg_type)); + NODE_API_ASSERT(env, arg_type == napi_function, "Argument must be a function"); + + // Dynamically create a function. + napi_value result; + NODE_API_CALL(env, + napi_create_function( + env, "TrackedFunction", NAPI_AUTO_LENGTH, TestFunctionName, NULL, + &result)); + + // Create a strong reference to the function we will call when the tracked + // function is about to be gc-ed. + napi_ref js_finalize_cb_ref; + NODE_API_CALL(env, + napi_create_reference(env, js_finalize_cb, 1, &js_finalize_cb_ref)); + + // Attach a finalizer to the dynamically created function and pass it the + // strong reference we created in the previous step. + NODE_API_CALL(env, + napi_wrap( + env, result, js_finalize_cb_ref, finalize_function, NULL, NULL)); + + return result; +} + +static napi_value TestBadReturnExceptionPending(napi_env env, napi_callback_info info) { + napi_throw_error(env, "throwing exception", "throwing exception"); + + // addons should only ever return a valid napi_value even if an + // exception occurs, but we have seen that the C++ wrapper + // with exceptions enabled sometimes returns an invalid value + // when an exception is thrown. Test that we ignore the return + // value then an exception is pending. We use 0xFFFFFFFF as a value + // that should never be a valid napi_value and node seems to + // crash if it is not ignored indicating that it is indeed invalid. + return (napi_value)(0xFFFFFFFFF); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value fn1; + NODE_API_CALL(env, napi_create_function( + env, NULL, NAPI_AUTO_LENGTH, TestCallFunction, NULL, &fn1)); + + napi_value fn2; + NODE_API_CALL(env, napi_create_function( + env, "Name", NAPI_AUTO_LENGTH, TestFunctionName, NULL, &fn2)); + + napi_value fn3; + NODE_API_CALL(env, napi_create_function( + env, "Name_extra", 5, TestFunctionName, NULL, &fn3)); + + napi_value fn4; + NODE_API_CALL(env, + napi_create_function( + env, "MakeTrackedFunction", NAPI_AUTO_LENGTH, MakeTrackedFunction, + NULL, &fn4)); + + napi_value fn5; + NODE_API_CALL(env, + napi_create_function( + env, "TestCreateFunctionParameters", NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, NULL, &fn5)); + + napi_value fn6; + NODE_API_CALL(env, + napi_create_function( + env, "TestBadReturnExceptionPending", NAPI_AUTO_LENGTH, + TestBadReturnExceptionPending, NULL, &fn6)); + + NODE_API_CALL(env, napi_set_named_property(env, exports, "TestCall", fn1)); + NODE_API_CALL(env, napi_set_named_property(env, exports, "TestName", fn2)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "TestNameShort", fn3)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "MakeTrackedFunction", fn4)); + + NODE_API_CALL(env, + napi_set_named_property( + env, exports, "TestCreateFunctionParameters", fn5)); + + NODE_API_CALL(env, + napi_set_named_property( + env, exports, "TestBadReturnExceptionPending", fn6)); + + return exports; +} +EXTERN_C_END