From a0f9aee3d99e9c7fc51d65e5fd922b29557a5fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 1 Mar 2026 11:09:41 +0100 Subject: [PATCH] feat: port test_cannot_run_js to CTS Builds two addons from the same C source with different NAPI_VERSION defines (10 and 9) to verify that finalizers attempting to access JS during shutdown receive napi_cannot_run_js or napi_pending_exception (respectively), or napi_ok if the event loop is still running. Co-Authored-By: Claude Sonnet 4.6 --- PORTING.md | 2 +- .../test_cannot_run_js/CMakeLists.txt | 5 ++ .../js-native-api/test_cannot_run_js/test.js | 21 ++++++ .../test_cannot_run_js/test_cannot_run_js.c | 66 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/js-native-api/test_cannot_run_js/CMakeLists.txt create mode 100644 tests/js-native-api/test_cannot_run_js/test.js create mode 100644 tests/js-native-api/test_cannot_run_js/test_cannot_run_js.c diff --git a/PORTING.md b/PORTING.md index 657cbb3..d30f345 100644 --- a/PORTING.md +++ b/PORTING.md @@ -49,7 +49,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `8_passing_wrapped` | Ported ✅ | Easy | | `test_array` | Ported ✅ | Easy | | `test_bigint` | Ported ✅ | Easy | -| `test_cannot_run_js` | Not ported | Medium | +| `test_cannot_run_js` | Ported ✅ | Medium | | `test_constructor` | Not ported | Medium | | `test_conversions` | Not ported | Medium | | `test_dataview` | Not ported | Medium | diff --git a/tests/js-native-api/test_cannot_run_js/CMakeLists.txt b/tests/js-native-api/test_cannot_run_js/CMakeLists.txt new file mode 100644 index 0000000..07297fa --- /dev/null +++ b/tests/js-native-api/test_cannot_run_js/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_cts_addon(test_cannot_run_js test_cannot_run_js.c) +target_compile_definitions(test_cannot_run_js PRIVATE NAPI_VERSION=10) + +add_node_api_cts_addon(test_pending_exception test_cannot_run_js.c) +target_compile_definitions(test_pending_exception PRIVATE NAPI_VERSION=9) diff --git a/tests/js-native-api/test_cannot_run_js/test.js b/tests/js-native-api/test_cannot_run_js/test.js new file mode 100644 index 0000000..918ba2d --- /dev/null +++ b/tests/js-native-api/test_cannot_run_js/test.js @@ -0,0 +1,21 @@ +// Test that `napi_get_named_property()` returns `napi_cannot_run_js` in +// experimental mode and `napi_pending_exception` otherwise. This test calls +// the add-on's `createRef()` method, which creates a strong reference to a JS +// function. When the process exits, it calls all reference finalizers. The +// finalizer for the strong reference created herein will attempt to call +// `napi_get_property()` on a property of the global object and will abort the +// process if the API doesn't return the correct status. + +const addon_v8 = loadAddon('test_pending_exception'); +const addon_new = loadAddon('test_cannot_run_js'); + +function runTests(addon) { + addon.createRef(function() { throw new Error('function should not have been called'); }); +} + +function runAllTests() { + runTests(addon_v8); + runTests(addon_new); +} + +runAllTests(); diff --git a/tests/js-native-api/test_cannot_run_js/test_cannot_run_js.c b/tests/js-native-api/test_cannot_run_js/test_cannot_run_js.c new file mode 100644 index 0000000..dddb8b5 --- /dev/null +++ b/tests/js-native-api/test_cannot_run_js/test_cannot_run_js.c @@ -0,0 +1,66 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "stdlib.h" + +static void Finalize(napi_env env, void* data, void* hint) { + napi_value global, set_timeout; + napi_ref* ref = data; + + NODE_API_BASIC_ASSERT_RETURN_VOID( + napi_delete_reference(env, *ref) == napi_ok, + "deleting reference in finalizer should succeed"); + NODE_API_BASIC_ASSERT_RETURN_VOID( + napi_get_global(env, &global) == napi_ok, + "getting global reference in finalizer should succeed"); + napi_status result = + napi_get_named_property(env, global, "setTimeout", &set_timeout); + + // The finalizer could be invoked either from check callbacks (as native + // immediates) if the event loop is still running (where napi_ok is returned) + // or during environment shutdown (where napi_cannot_run_js or + // napi_pending_exception is returned). This is not deterministic from + // the point of view of the addon. + +#if NAPI_VERSION > 9 + NODE_API_BASIC_ASSERT_RETURN_VOID( + result == napi_cannot_run_js || result == napi_ok, + "getting named property from global in finalizer should succeed " + "or return napi_cannot_run_js"); +#else + NODE_API_BASIC_ASSERT_RETURN_VOID( + result == napi_pending_exception || result == napi_ok, + "getting named property from global in finalizer should succeed " + "or return napi_pending_exception"); +#endif // NAPI_VERSION > 9 + free(ref); +} + +static napi_value CreateRef(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value cb; + napi_valuetype value_type; + napi_ref* ref = malloc(sizeof(*ref)); + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Function takes only one argument"); + NODE_API_CALL(env, napi_typeof(env, cb, &value_type)); + NODE_API_ASSERT( + env, value_type == napi_function, "argument must be function"); + NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, Finalize, NULL, ref)); + return cb; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), + }; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END