From d71251589d7df50080ad66c1095e28fbef3daeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 28 Feb 2026 13:59:41 +0100 Subject: [PATCH 1/8] feat: port 17 easy js-native-api tests to CTS Port all "Easy" difficulty engine-specific tests from Node.js: 3_callbacks, 4_object_factory, 5_function_factory, 7_factory_wrap, 8_passing_wrapped, test_array, test_bigint, test_dataview, test_date, test_handle_scope, test_instance_data, test_new_target, test_number, test_promise, test_properties, test_reference_double_free, test_symbol. C/C++ sources are copied from Node.js with minimal changes (entry_point.h macro). JS tests are adapted to use CTS globals (loadAddon, assert, gcUntil) instead of Node.js test harness (common.js, require). test_dataview: removed SharedArrayBuffer tests that depend on experimental node_api_is_sharedarraybuffer API (not in stable node-api-headers). Also updates node-api-headers from 1.7.0 to 1.8.0. Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 8 +- package.json | 2 +- tests/js-native-api/3_callbacks/3_callbacks.c | 58 ++++++ .../js-native-api/3_callbacks/CMakeLists.txt | 1 + tests/js-native-api/3_callbacks/test.js | 26 +++ .../4_object_factory/4_object_factory.c | 24 +++ .../4_object_factory/CMakeLists.txt | 1 + tests/js-native-api/4_object_factory/test.js | 6 + .../5_function_factory/5_function_factory.c | 24 +++ .../5_function_factory/CMakeLists.txt | 1 + .../js-native-api/5_function_factory/test.js | 5 + .../7_factory_wrap/7_factory_wrap.cc | 32 +++ .../7_factory_wrap/CMakeLists.txt | 1 + .../js-native-api/7_factory_wrap/myobject.cc | 101 ++++++++++ tests/js-native-api/7_factory_wrap/myobject.h | 27 +++ tests/js-native-api/7_factory_wrap/test.js | 24 +++ .../8_passing_wrapped/8_passing_wrapped.cc | 61 ++++++ .../8_passing_wrapped/CMakeLists.txt | 1 + .../8_passing_wrapped/myobject.cc | 80 ++++++++ .../8_passing_wrapped/myobject.h | 26 +++ tests/js-native-api/8_passing_wrapped/test.js | 12 ++ tests/js-native-api/test_array/CMakeLists.txt | 1 + tests/js-native-api/test_array/test.js | 55 +++++ tests/js-native-api/test_array/test_array.c | 188 ++++++++++++++++++ .../js-native-api/test_bigint/CMakeLists.txt | 1 + tests/js-native-api/test_bigint/test.js | 50 +++++ tests/js-native-api/test_bigint/test_bigint.c | 159 +++++++++++++++ .../test_dataview/CMakeLists.txt | 1 + tests/js-native-api/test_dataview/test.js | 21 ++ .../test_dataview/test_dataview.c | 104 ++++++++++ tests/js-native-api/test_date/CMakeLists.txt | 1 + tests/js-native-api/test_date/test.js | 15 ++ tests/js-native-api/test_date/test_date.c | 64 ++++++ .../test_handle_scope/CMakeLists.txt | 1 + tests/js-native-api/test_handle_scope/test.js | 14 ++ .../test_handle_scope/test_handle_scope.c | 86 ++++++++ .../test_instance_data/CMakeLists.txt | 1 + .../js-native-api/test_instance_data/test.js | 5 + .../test_instance_data/test_instance_data.c | 96 +++++++++ .../test_new_target/CMakeLists.txt | 1 + tests/js-native-api/test_new_target/test.js | 18 ++ .../test_new_target/test_new_target.c | 70 +++++++ .../js-native-api/test_number/CMakeLists.txt | 1 + tests/js-native-api/test_number/test.js | 131 ++++++++++++ tests/js-native-api/test_number/test_null.c | 77 +++++++ tests/js-native-api/test_number/test_null.h | 8 + tests/js-native-api/test_number/test_null.js | 16 ++ tests/js-native-api/test_number/test_number.c | 110 ++++++++++ .../js-native-api/test_promise/CMakeLists.txt | 1 + tests/js-native-api/test_promise/test.js | 72 +++++++ .../js-native-api/test_promise/test_promise.c | 64 ++++++ .../test_properties/CMakeLists.txt | 1 + tests/js-native-api/test_properties/test.js | 66 ++++++ .../test_properties/test_properties.c | 113 +++++++++++ .../test_reference_double_free/CMakeLists.txt | 1 + .../test_reference_double_free/test.js | 9 + .../test_reference_double_free.c | 90 +++++++++ .../test_reference_double_free/test_wrap.js | 8 + .../js-native-api/test_symbol/CMakeLists.txt | 1 + tests/js-native-api/test_symbol/test1.js | 15 ++ tests/js-native-api/test_symbol/test2.js | 13 ++ tests/js-native-api/test_symbol/test3.js | 15 ++ tests/js-native-api/test_symbol/test_symbol.c | 38 ++++ 63 files changed, 2318 insertions(+), 5 deletions(-) create mode 100644 tests/js-native-api/3_callbacks/3_callbacks.c create mode 100644 tests/js-native-api/3_callbacks/CMakeLists.txt create mode 100644 tests/js-native-api/3_callbacks/test.js create mode 100644 tests/js-native-api/4_object_factory/4_object_factory.c create mode 100644 tests/js-native-api/4_object_factory/CMakeLists.txt create mode 100644 tests/js-native-api/4_object_factory/test.js create mode 100644 tests/js-native-api/5_function_factory/5_function_factory.c create mode 100644 tests/js-native-api/5_function_factory/CMakeLists.txt create mode 100644 tests/js-native-api/5_function_factory/test.js create mode 100644 tests/js-native-api/7_factory_wrap/7_factory_wrap.cc create mode 100644 tests/js-native-api/7_factory_wrap/CMakeLists.txt create mode 100644 tests/js-native-api/7_factory_wrap/myobject.cc create mode 100644 tests/js-native-api/7_factory_wrap/myobject.h create mode 100644 tests/js-native-api/7_factory_wrap/test.js create mode 100644 tests/js-native-api/8_passing_wrapped/8_passing_wrapped.cc create mode 100644 tests/js-native-api/8_passing_wrapped/CMakeLists.txt create mode 100644 tests/js-native-api/8_passing_wrapped/myobject.cc create mode 100644 tests/js-native-api/8_passing_wrapped/myobject.h create mode 100644 tests/js-native-api/8_passing_wrapped/test.js create mode 100644 tests/js-native-api/test_array/CMakeLists.txt create mode 100644 tests/js-native-api/test_array/test.js create mode 100644 tests/js-native-api/test_array/test_array.c create mode 100644 tests/js-native-api/test_bigint/CMakeLists.txt create mode 100644 tests/js-native-api/test_bigint/test.js create mode 100644 tests/js-native-api/test_bigint/test_bigint.c create mode 100644 tests/js-native-api/test_dataview/CMakeLists.txt create mode 100644 tests/js-native-api/test_dataview/test.js create mode 100644 tests/js-native-api/test_dataview/test_dataview.c create mode 100644 tests/js-native-api/test_date/CMakeLists.txt create mode 100644 tests/js-native-api/test_date/test.js create mode 100644 tests/js-native-api/test_date/test_date.c create mode 100644 tests/js-native-api/test_handle_scope/CMakeLists.txt create mode 100644 tests/js-native-api/test_handle_scope/test.js create mode 100644 tests/js-native-api/test_handle_scope/test_handle_scope.c create mode 100644 tests/js-native-api/test_instance_data/CMakeLists.txt create mode 100644 tests/js-native-api/test_instance_data/test.js create mode 100644 tests/js-native-api/test_instance_data/test_instance_data.c create mode 100644 tests/js-native-api/test_new_target/CMakeLists.txt create mode 100644 tests/js-native-api/test_new_target/test.js create mode 100644 tests/js-native-api/test_new_target/test_new_target.c create mode 100644 tests/js-native-api/test_number/CMakeLists.txt create mode 100644 tests/js-native-api/test_number/test.js create mode 100644 tests/js-native-api/test_number/test_null.c create mode 100644 tests/js-native-api/test_number/test_null.h create mode 100644 tests/js-native-api/test_number/test_null.js create mode 100644 tests/js-native-api/test_number/test_number.c create mode 100644 tests/js-native-api/test_promise/CMakeLists.txt create mode 100644 tests/js-native-api/test_promise/test.js create mode 100644 tests/js-native-api/test_promise/test_promise.c create mode 100644 tests/js-native-api/test_properties/CMakeLists.txt create mode 100644 tests/js-native-api/test_properties/test.js create mode 100644 tests/js-native-api/test_properties/test_properties.c create mode 100644 tests/js-native-api/test_reference_double_free/CMakeLists.txt create mode 100644 tests/js-native-api/test_reference_double_free/test.js create mode 100644 tests/js-native-api/test_reference_double_free/test_reference_double_free.c create mode 100644 tests/js-native-api/test_reference_double_free/test_wrap.js create mode 100644 tests/js-native-api/test_symbol/CMakeLists.txt create mode 100644 tests/js-native-api/test_symbol/test1.js create mode 100644 tests/js-native-api/test_symbol/test2.js create mode 100644 tests/js-native-api/test_symbol/test3.js create mode 100644 tests/js-native-api/test_symbol/test_symbol.c diff --git a/package-lock.json b/package-lock.json index d8ec84f..39e6df5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "devDependencies": { "@types/node": "^24.10.1", "eslint": "^9.39.1", - "node-api-headers": "^1.7.0" + "node-api-headers": "^1.8.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -886,9 +886,9 @@ "license": "MIT" }, "node_modules/node-api-headers": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.7.0.tgz", - "integrity": "sha512-uJMGdkhVwu9+I3UsVvI3KW6ICAy/yDfsu5Br9rSnTtY3WpoaComXvKloiV5wtx0Md2rn0B9n29Ys2WMNwWxj9A==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.8.0.tgz", + "integrity": "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 1590d7b..8ef19e8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@types/node": "^24.10.1", "eslint": "^9.39.1", - "node-api-headers": "^1.7.0" + "node-api-headers": "^1.8.0" }, "dependencies": { "amaro": "^1.1.5" diff --git a/tests/js-native-api/3_callbacks/3_callbacks.c b/tests/js-native-api/3_callbacks/3_callbacks.c new file mode 100644 index 0000000..44bd245 --- /dev/null +++ b/tests/js-native-api/3_callbacks/3_callbacks.c @@ -0,0 +1,58 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value RunCallback(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, + "Wrong number of arguments. Expects a single argument."); + + 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_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NODE_API_ASSERT(env, valuetype1 == napi_undefined, + "Additional arguments should be undefined."); + + napi_value argv[1]; + const char* str = "hello world"; + size_t str_len = strlen(str); + NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, argv)); + + napi_value global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value cb = args[0]; + NODE_API_CALL(env, napi_call_function(env, global, cb, 1, argv, NULL)); + + return NULL; +} + +static napi_value RunCallbackWithRecv(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value cb = args[0]; + napi_value recv = args[1]; + NODE_API_CALL(env, napi_call_function(env, recv, cb, 0, NULL, NULL)); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[2] = { + DECLARE_NODE_API_PROPERTY("RunCallback", RunCallback), + DECLARE_NODE_API_PROPERTY("RunCallbackWithRecv", RunCallbackWithRecv), + }; + NODE_API_CALL(env, napi_define_properties(env, exports, 2, desc)); + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/3_callbacks/CMakeLists.txt b/tests/js-native-api/3_callbacks/CMakeLists.txt new file mode 100644 index 0000000..b7b0aa3 --- /dev/null +++ b/tests/js-native-api/3_callbacks/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(3_callbacks 3_callbacks.c) diff --git a/tests/js-native-api/3_callbacks/test.js b/tests/js-native-api/3_callbacks/test.js new file mode 100644 index 0000000..8dfc464 --- /dev/null +++ b/tests/js-native-api/3_callbacks/test.js @@ -0,0 +1,26 @@ +'use strict'; +const addon = loadAddon('3_callbacks'); + +let called = false; +addon.RunCallback((msg) => { + assert.strictEqual(msg, 'hello world'); + called = true; +}); +assert(called); + +function testRecv(desiredRecv) { + let recvCalled = false; + addon.RunCallbackWithRecv(function() { + assert.strictEqual(this, desiredRecv); + recvCalled = true; + }, desiredRecv); + assert(recvCalled); +} + +testRecv(undefined); +testRecv(null); +testRecv(5); +testRecv(true); +testRecv('Hello'); +testRecv([]); +testRecv({}); diff --git a/tests/js-native-api/4_object_factory/4_object_factory.c b/tests/js-native-api/4_object_factory/4_object_factory.c new file mode 100644 index 0000000..8fd6090 --- /dev/null +++ b/tests/js-native-api/4_object_factory/4_object_factory.c @@ -0,0 +1,24 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value obj; + NODE_API_CALL(env, napi_create_object(env, &obj)); + + NODE_API_CALL(env, napi_set_named_property(env, obj, "msg", args[0])); + + return obj; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, + napi_create_function(env, "exports", -1, CreateObject, NULL, &exports)); + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/4_object_factory/CMakeLists.txt b/tests/js-native-api/4_object_factory/CMakeLists.txt new file mode 100644 index 0000000..8beff02 --- /dev/null +++ b/tests/js-native-api/4_object_factory/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(4_object_factory 4_object_factory.c) diff --git a/tests/js-native-api/4_object_factory/test.js b/tests/js-native-api/4_object_factory/test.js new file mode 100644 index 0000000..179e91c --- /dev/null +++ b/tests/js-native-api/4_object_factory/test.js @@ -0,0 +1,6 @@ +'use strict'; +const addon = loadAddon('4_object_factory'); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +assert.strictEqual(`${obj1.msg} ${obj2.msg}`, 'hello world'); diff --git a/tests/js-native-api/5_function_factory/5_function_factory.c b/tests/js-native-api/5_function_factory/5_function_factory.c new file mode 100644 index 0000000..8c2bdac --- /dev/null +++ b/tests/js-native-api/5_function_factory/5_function_factory.c @@ -0,0 +1,24 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value MyFunction(napi_env env, napi_callback_info info) { + napi_value str; + NODE_API_CALL(env, napi_create_string_utf8(env, "hello world", -1, &str)); + return str; +} + +static napi_value CreateFunction(napi_env env, napi_callback_info info) { + napi_value fn; + NODE_API_CALL(env, + napi_create_function(env, "theFunction", -1, MyFunction, NULL, &fn)); + return fn; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, + napi_create_function(env, "exports", -1, CreateFunction, NULL, &exports)); + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/5_function_factory/CMakeLists.txt b/tests/js-native-api/5_function_factory/CMakeLists.txt new file mode 100644 index 0000000..30aaa31 --- /dev/null +++ b/tests/js-native-api/5_function_factory/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(5_function_factory 5_function_factory.c) diff --git a/tests/js-native-api/5_function_factory/test.js b/tests/js-native-api/5_function_factory/test.js new file mode 100644 index 0000000..6741a8a --- /dev/null +++ b/tests/js-native-api/5_function_factory/test.js @@ -0,0 +1,5 @@ +'use strict'; +const addon = loadAddon('5_function_factory'); + +const fn = addon(); +assert.strictEqual(fn(), 'hello world'); diff --git a/tests/js-native-api/7_factory_wrap/7_factory_wrap.cc b/tests/js-native-api/7_factory_wrap/7_factory_wrap.cc new file mode 100644 index 0000000..5fb7a66 --- /dev/null +++ b/tests/js-native-api/7_factory_wrap/7_factory_wrap.cc @@ -0,0 +1,32 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "myobject.h" + +napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + napi_value instance; + NODE_API_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, MyObject::Init(env)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_GETTER("finalizeCount", MyObject::GetFinalizeCount), + DECLARE_NODE_API_PROPERTY("createObject", CreateObject), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/7_factory_wrap/CMakeLists.txt b/tests/js-native-api/7_factory_wrap/CMakeLists.txt new file mode 100644 index 0000000..bfa00f1 --- /dev/null +++ b/tests/js-native-api/7_factory_wrap/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(7_factory_wrap 7_factory_wrap.cc myobject.cc) diff --git a/tests/js-native-api/7_factory_wrap/myobject.cc b/tests/js-native-api/7_factory_wrap/myobject.cc new file mode 100644 index 0000000..a1a0097 --- /dev/null +++ b/tests/js-native-api/7_factory_wrap/myobject.cc @@ -0,0 +1,101 @@ +#include "myobject.h" +#include "../common.h" + +static int finalize_count = 0; + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + ++finalize_count; + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +napi_value MyObject::GetFinalizeCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalize_count, &result)); + return result; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("plusOne", PlusOne), + }; + + napi_value cons; + status = napi_define_class( + env, "MyObject", -1, New, nullptr, 1, properties, &cons); + if (status != napi_ok) return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + MyObject* obj = new MyObject(); + + if (valuetype == napi_undefined) { + obj->counter_ = 0; + } else { + NODE_API_CALL(env, napi_get_value_uint32(env, args[0], &obj->counter_)); + } + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap( + env, _this, obj, MyObject::Destructor, nullptr /* finalize_hint */, + &obj->wrapper_)); + + return _this; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->counter_ += 1; + + napi_value num; + NODE_API_CALL(env, napi_create_uint32(env, obj->counter_, &num)); + + return num; +} diff --git a/tests/js-native-api/7_factory_wrap/myobject.h b/tests/js-native-api/7_factory_wrap/myobject.h new file mode 100644 index 0000000..455ec1c --- /dev/null +++ b/tests/js-native-api/7_factory_wrap/myobject.h @@ -0,0 +1,27 @@ +#ifndef TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_value GetFinalizeCount(napi_env env, napi_callback_info info); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + uint32_t counter_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ diff --git a/tests/js-native-api/7_factory_wrap/test.js b/tests/js-native-api/7_factory_wrap/test.js new file mode 100644 index 0000000..de3723b --- /dev/null +++ b/tests/js-native-api/7_factory_wrap/test.js @@ -0,0 +1,24 @@ +'use strict'; +const test = loadAddon('7_factory_wrap'); + +assert.strictEqual(test.finalizeCount, 0); + +async function runGCTests() { + (() => { + const obj = test.createObject(10); + assert.strictEqual(obj.plusOne(), 11); + assert.strictEqual(obj.plusOne(), 12); + assert.strictEqual(obj.plusOne(), 13); + })(); + await gcUntil('test 1', () => (test.finalizeCount === 1)); + + (() => { + const obj2 = test.createObject(20); + assert.strictEqual(obj2.plusOne(), 21); + assert.strictEqual(obj2.plusOne(), 22); + assert.strictEqual(obj2.plusOne(), 23); + })(); + await gcUntil('test 2', () => (test.finalizeCount === 2)); +} + +await runGCTests(); diff --git a/tests/js-native-api/8_passing_wrapped/8_passing_wrapped.cc b/tests/js-native-api/8_passing_wrapped/8_passing_wrapped.cc new file mode 100644 index 0000000..1a3e6d1 --- /dev/null +++ b/tests/js-native-api/8_passing_wrapped/8_passing_wrapped.cc @@ -0,0 +1,61 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "myobject.h" + +extern size_t finalize_count; + +static napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + napi_value instance; + NODE_API_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +static napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + MyObject* obj1; + NODE_API_CALL(env, + napi_unwrap(env, args[0], reinterpret_cast(&obj1))); + + MyObject* obj2; + NODE_API_CALL(env, + napi_unwrap(env, args[1], reinterpret_cast(&obj2))); + + napi_value sum; + NODE_API_CALL(env, napi_create_double(env, obj1->Val() + obj2->Val(), &sum)); + + return sum; +} + +static napi_value FinalizeCount(napi_env env, napi_callback_info info) { + napi_value return_value; + NODE_API_CALL(env, napi_create_uint32(env, finalize_count, &return_value)); + return return_value; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + MyObject::Init(env); + + napi_property_descriptor desc[] = { + DECLARE_NODE_API_PROPERTY("createObject", CreateObject), + DECLARE_NODE_API_PROPERTY("add", Add), + DECLARE_NODE_API_PROPERTY("finalizeCount", FinalizeCount), + }; + + NODE_API_CALL(env, + napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/8_passing_wrapped/CMakeLists.txt b/tests/js-native-api/8_passing_wrapped/CMakeLists.txt new file mode 100644 index 0000000..952d4d2 --- /dev/null +++ b/tests/js-native-api/8_passing_wrapped/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(8_passing_wrapped 8_passing_wrapped.cc myobject.cc) diff --git a/tests/js-native-api/8_passing_wrapped/myobject.cc b/tests/js-native-api/8_passing_wrapped/myobject.cc new file mode 100644 index 0000000..6ca61bb --- /dev/null +++ b/tests/js-native-api/8_passing_wrapped/myobject.cc @@ -0,0 +1,80 @@ +#include "myobject.h" +#include "../common.h" + +size_t finalize_count = 0; + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + finalize_count++; + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor( + napi_env env, void* nativeObject, void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = napi_define_class( + env, "MyObject", -1, New, nullptr, 0, nullptr, &cons); + if (status != napi_ok) return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject* obj = new MyObject(); + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype == napi_undefined) { + obj->val_ = 0; + } else { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &obj->val_)); + } + + obj->env_ = env; + + // The below call to napi_wrap() must request a reference to the wrapped + // object via the out-parameter, because this ensures that we test the code + // path that deals with a reference that is destroyed from its own finalizer. + NODE_API_CALL(env, + napi_wrap(env, _this, obj, MyObject::Destructor, + nullptr /* finalize_hint */, &obj->wrapper_)); + + return _this; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} diff --git a/tests/js-native-api/8_passing_wrapped/myobject.h b/tests/js-native-api/8_passing_wrapped/myobject.h new file mode 100644 index 0000000..445cf56 --- /dev/null +++ b/tests/js-native-api/8_passing_wrapped/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + double val_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ diff --git a/tests/js-native-api/8_passing_wrapped/test.js b/tests/js-native-api/8_passing_wrapped/test.js new file mode 100644 index 0000000..6197d31 --- /dev/null +++ b/tests/js-native-api/8_passing_wrapped/test.js @@ -0,0 +1,12 @@ +'use strict'; +const addon = loadAddon('8_passing_wrapped'); + +let obj1 = addon.createObject(10); +let obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); +assert.strictEqual(result, 30); + +// Make sure the native destructor gets called. +obj1 = null; +obj2 = null; +await gcUntil('8_passing_wrapped', () => (addon.finalizeCount() === 2)); diff --git a/tests/js-native-api/test_array/CMakeLists.txt b/tests/js-native-api/test_array/CMakeLists.txt new file mode 100644 index 0000000..277b74a --- /dev/null +++ b/tests/js-native-api/test_array/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_array test_array.c) diff --git a/tests/js-native-api/test_array/test.js b/tests/js-native-api/test_array/test.js new file mode 100644 index 0000000..d20c0a8 --- /dev/null +++ b/tests/js-native-api/test_array/test.js @@ -0,0 +1,55 @@ +'use strict'; +const test_array = loadAddon('test_array'); + +const array = [ + 1, + 9, + 48, + 13493, + 9459324, + { name: 'hello' }, + [ + 'world', + 'node', + 'abi', + ], +]; + +assert.throws( + () => { + test_array.TestGetElement(array, array.length + 1); + }, + /^Error: assertion \(\(\(uint32_t\)index < length\)\) failed: Index out of bounds!$/, +); + +assert.throws( + () => { + test_array.TestGetElement(array, -2); + }, + /^Error: assertion \(index >= 0\) failed: Invalid index\. Expects a positive integer\.$/, +); + +array.forEach(function(element, index) { + assert.strictEqual(test_array.TestGetElement(array, index), element); +}); + +assert.deepStrictEqual(test_array.New(array), array); + +assert(test_array.TestHasElement(array, 0)); +assert.strictEqual(test_array.TestHasElement(array, array.length + 1), false); + +assert(test_array.NewWithLength(0) instanceof Array); +assert(test_array.NewWithLength(1) instanceof Array); +// Check max allowed length for an array 2^32 -1 +assert(test_array.NewWithLength(4294967295) instanceof Array); + +{ + // Verify that array elements can be deleted. + const arr = ['a', 'b', 'c', 'd']; + + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, true); + assert.strictEqual(test_array.TestDeleteElement(arr, 2), true); + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, false); +} diff --git a/tests/js-native-api/test_array/test_array.c b/tests/js-native-api/test_array/test_array.c new file mode 100644 index 0000000..a451502 --- /dev/null +++ b/tests/js-native-api/test_array/test_array.c @@ -0,0 +1,188 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestGetElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + + NODE_API_ASSERT(env, index >= 0, "Invalid index. Expects a positive integer."); + + bool isarray; + NODE_API_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + uint32_t length; + NODE_API_CALL(env, napi_get_array_length(env, array, &length)); + + NODE_API_ASSERT(env, ((uint32_t)index < length), "Index out of bounds!"); + + napi_value ret; + NODE_API_CALL(env, napi_get_element(env, array, index, &ret)); + + return ret; +} + +static napi_value TestHasElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + + bool isarray; + NODE_API_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + bool has_element; + NODE_API_CALL(env, napi_has_element(env, array, index, &has_element)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, has_element, &ret)); + + return ret; +} + +static napi_value TestDeleteElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + bool result; + napi_value ret; + + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + NODE_API_CALL(env, napi_is_array(env, array, &result)); + + if (!result) { + return NULL; + } + + NODE_API_CALL(env, napi_delete_element(env, array, index, &result)); + NODE_API_CALL(env, napi_get_boolean(env, result, &ret)); + + return ret; +} + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_value ret; + NODE_API_CALL(env, napi_create_array(env, &ret)); + + uint32_t i, length; + NODE_API_CALL(env, napi_get_array_length(env, args[0], &length)); + + for (i = 0; i < length; i++) { + napi_value e; + NODE_API_CALL(env, napi_get_element(env, args[0], i, &e)); + NODE_API_CALL(env, napi_set_element(env, ret, i, e)); + } + + return ret; +} + +static napi_value NewWithLength(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects an integer the first argument."); + + int32_t array_length; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &array_length)); + + napi_value ret; + NODE_API_CALL(env, napi_create_array_with_length(env, array_length, &ret)); + + return ret; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("TestGetElement", TestGetElement), + DECLARE_NODE_API_PROPERTY("TestHasElement", TestHasElement), + DECLARE_NODE_API_PROPERTY("TestDeleteElement", TestDeleteElement), + DECLARE_NODE_API_PROPERTY("New", New), + DECLARE_NODE_API_PROPERTY("NewWithLength", NewWithLength), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_bigint/CMakeLists.txt b/tests/js-native-api/test_bigint/CMakeLists.txt new file mode 100644 index 0000000..cb43b1f --- /dev/null +++ b/tests/js-native-api/test_bigint/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_bigint test_bigint.c) diff --git a/tests/js-native-api/test_bigint/test.js b/tests/js-native-api/test_bigint/test.js new file mode 100644 index 0000000..be779f6 --- /dev/null +++ b/tests/js-native-api/test_bigint/test.js @@ -0,0 +1,50 @@ +'use strict'; +const { + IsLossless, + TestInt64, + TestUint64, + TestWords, + CreateTooBigBigInt, + MakeBigIntWordsThrow, +} = loadAddon('test_bigint'); + +[ + 0n, + -0n, + 1n, + -1n, + 100n, + 2121n, + -1233n, + 986583n, + -976675n, + 98765432213456789876546896323445679887645323232436587988766545658n, + -4350987086545760976737453646576078997096876957864353245245769809n, +].forEach((num) => { + if (num > -(2n ** 63n) && num < 2n ** 63n) { + assert.strictEqual(TestInt64(num), num); + assert.strictEqual(IsLossless(num, true), true); + } else { + assert.strictEqual(IsLossless(num, true), false); + } + + if (num >= 0 && num < 2n ** 64n) { + assert.strictEqual(TestUint64(num), num); + assert.strictEqual(IsLossless(num, false), true); + } else { + assert.strictEqual(IsLossless(num, false), false); + } + + assert.strictEqual(num, TestWords(num)); +}); + +assert.throws(() => CreateTooBigBigInt(), { + name: 'Error', + message: 'Invalid argument', +}); + +// Test that we correctly forward exceptions from the engine. +assert.throws(() => MakeBigIntWordsThrow(), { + name: 'RangeError', + message: 'Maximum BigInt size exceeded', +}); diff --git a/tests/js-native-api/test_bigint/test_bigint.c b/tests/js-native-api/test_bigint/test_bigint.c new file mode 100644 index 0000000..2c61e0b --- /dev/null +++ b/tests/js-native-api/test_bigint/test_bigint.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value IsLossless(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + bool is_signed; + NODE_API_CALL(env, napi_get_value_bool(env, args[1], &is_signed)); + + bool lossless; + + if (is_signed) { + int64_t input; + NODE_API_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + } else { + uint64_t input; + NODE_API_CALL(env, napi_get_value_bigint_uint64(env, args[0], &input, &lossless)); + } + + napi_value output; + NODE_API_CALL(env, napi_get_boolean(env, lossless, &output)); + + return output; +} + +static napi_value TestInt64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + int64_t input; + bool lossless; + NODE_API_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_int64(env, input, &output)); + + return output; +} + +static napi_value TestUint64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + uint64_t input; + bool lossless; + NODE_API_CALL(env, napi_get_value_bigint_uint64( + env, args[0], &input, &lossless)); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_uint64(env, input, &output)); + + return output; +} + +static napi_value TestWords(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + size_t expected_word_count; + NODE_API_CALL(env, napi_get_value_bigint_words( + env, args[0], NULL, &expected_word_count, NULL)); + + int sign_bit; + size_t word_count = 10; + uint64_t words[10]; + + NODE_API_CALL(env, napi_get_value_bigint_words( + env, args[0], &sign_bit, &word_count, words)); + + NODE_API_ASSERT(env, word_count == expected_word_count, + "word counts do not match"); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +// throws RangeError +static napi_value CreateTooBigBigInt(napi_env env, napi_callback_info info) { + int sign_bit = 0; + size_t word_count = SIZE_MAX; + uint64_t words[10] = {0}; + + napi_value output; + + NODE_API_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +// Test that we correctly forward exceptions from the engine. +static napi_value MakeBigIntWordsThrow(napi_env env, napi_callback_info info) { + uint64_t words[10] = {0}; + napi_value output; + + napi_status status = napi_create_bigint_words(env, + 0, + INT_MAX, + words, + &output); + if (status != napi_pending_exception) + napi_throw_error(env, NULL, "Expected status `napi_pending_exception`"); + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("IsLossless", IsLossless), + DECLARE_NODE_API_PROPERTY("TestInt64", TestInt64), + DECLARE_NODE_API_PROPERTY("TestUint64", TestUint64), + DECLARE_NODE_API_PROPERTY("TestWords", TestWords), + DECLARE_NODE_API_PROPERTY("CreateTooBigBigInt", CreateTooBigBigInt), + DECLARE_NODE_API_PROPERTY("MakeBigIntWordsThrow", MakeBigIntWordsThrow), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_dataview/CMakeLists.txt b/tests/js-native-api/test_dataview/CMakeLists.txt new file mode 100644 index 0000000..4411f2e --- /dev/null +++ b/tests/js-native-api/test_dataview/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_dataview test_dataview.c) diff --git a/tests/js-native-api/test_dataview/test.js b/tests/js-native-api/test_dataview/test.js new file mode 100644 index 0000000..c267919 --- /dev/null +++ b/tests/js-native-api/test_dataview/test.js @@ -0,0 +1,21 @@ +'use strict'; +const test_dataview = loadAddon('test_dataview'); + +// Test for creating dataview with ArrayBuffer +{ + const buffer = new ArrayBuffer(128); + const template = Reflect.construct(DataView, [buffer]); + + const theDataview = test_dataview.CreateDataViewFromJSDataView(template); + assert(theDataview instanceof DataView, + `Expect ${theDataview} to be a DataView`); +} + +// Test for creating dataview with ArrayBuffer and invalid range +{ + const buffer = new ArrayBuffer(128); + assert.throws(() => { + test_dataview.CreateDataView(buffer, 10, 200); + }, RangeError); +} + diff --git a/tests/js-native-api/test_dataview/test_dataview.c b/tests/js-native-api/test_dataview/test_dataview.c new file mode 100644 index 0000000..ff774fa --- /dev/null +++ b/tests/js-native-api/test_dataview/test_dataview.c @@ -0,0 +1,104 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value CreateDataView(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args [3]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 3, "Wrong number of arguments"); + + napi_valuetype valuetype0; + napi_value arraybuffer = args[0]; + + NODE_API_CALL(env, napi_typeof(env, arraybuffer, &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a ArrayBuffer as the first " + "argument."); + + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, arraybuffer, &is_arraybuffer)); + + NODE_API_ASSERT(env, + is_arraybuffer, + "Wrong type of arguments. Expects an ArrayBuffer as the " + "first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + size_t byte_offset = 0; + NODE_API_CALL(env, napi_get_value_uint32(env, args[1], (uint32_t*)(&byte_offset))); + + napi_valuetype valuetype2; + NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NODE_API_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + size_t length = 0; + NODE_API_CALL(env, napi_get_value_uint32(env, args[2], (uint32_t*)(&length))); + + napi_value output_dataview; + NODE_API_CALL(env, + napi_create_dataview(env, length, arraybuffer, + byte_offset, &output_dataview)); + + return output_dataview; +} + +static napi_value CreateDataViewFromJSDataView(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args [1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + napi_value input_dataview = args[0]; + + NODE_API_CALL(env, napi_typeof(env, input_dataview, &valuetype)); + NODE_API_ASSERT(env, valuetype == napi_object, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + + bool is_dataview; + NODE_API_CALL(env, napi_is_dataview(env, input_dataview, &is_dataview)); + NODE_API_ASSERT(env, is_dataview, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + size_t byte_offset = 0; + size_t length = 0; + napi_value buffer; + NODE_API_CALL(env, + napi_get_dataview_info(env, input_dataview, &length, NULL, + &buffer, &byte_offset)); + + napi_value output_dataview; + NODE_API_CALL(env, + napi_create_dataview(env, length, buffer, + byte_offset, &output_dataview)); + + + return output_dataview; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("CreateDataView", CreateDataView), + DECLARE_NODE_API_PROPERTY("CreateDataViewFromJSDataView", + CreateDataViewFromJSDataView) + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_date/CMakeLists.txt b/tests/js-native-api/test_date/CMakeLists.txt new file mode 100644 index 0000000..463a246 --- /dev/null +++ b/tests/js-native-api/test_date/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_date test_date.c) diff --git a/tests/js-native-api/test_date/test.js b/tests/js-native-api/test_date/test.js new file mode 100644 index 0000000..105c70a --- /dev/null +++ b/tests/js-native-api/test_date/test.js @@ -0,0 +1,15 @@ +'use strict'; +const test_date = loadAddon('test_date'); + +const dateTypeTestDate = test_date.createDate(1549183351); +assert.strictEqual(test_date.isDate(dateTypeTestDate), true); + +assert.strictEqual(test_date.isDate(new Date(1549183351)), true); + +assert.strictEqual(test_date.isDate(2.4), false); +assert.strictEqual(test_date.isDate('not a date'), false); +assert.strictEqual(test_date.isDate(undefined), false); +assert.strictEqual(test_date.isDate(null), false); +assert.strictEqual(test_date.isDate({}), false); + +assert.strictEqual(test_date.getDateValue(new Date(1549183351)), 1549183351); diff --git a/tests/js-native-api/test_date/test_date.c b/tests/js-native-api/test_date/test_date.c new file mode 100644 index 0000000..ef87d6d --- /dev/null +++ b/tests/js-native-api/test_date/test_date.c @@ -0,0 +1,64 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value createDate(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + double time; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &time)); + + napi_value date; + NODE_API_CALL(env, napi_create_date(env, time, &date)); + + return date; +} + +static napi_value isDate(napi_env env, napi_callback_info info) { + napi_value date, result; + size_t argc = 1; + bool is_date; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &date, NULL, NULL)); + NODE_API_CALL(env, napi_is_date(env, date, &is_date)); + NODE_API_CALL(env, napi_get_boolean(env, is_date, &result)); + + return result; +} + +static napi_value getDateValue(napi_env env, napi_callback_info info) { + napi_value date, result; + size_t argc = 1; + double value; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &date, NULL, NULL)); + NODE_API_CALL(env, napi_get_date_value(env, date, &value)); + NODE_API_CALL(env, napi_create_double(env, value, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("createDate", createDate), + DECLARE_NODE_API_PROPERTY("isDate", isDate), + DECLARE_NODE_API_PROPERTY("getDateValue", getDateValue), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_handle_scope/CMakeLists.txt b/tests/js-native-api/test_handle_scope/CMakeLists.txt new file mode 100644 index 0000000..228acb6 --- /dev/null +++ b/tests/js-native-api/test_handle_scope/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_handle_scope test_handle_scope.c) diff --git a/tests/js-native-api/test_handle_scope/test.js b/tests/js-native-api/test_handle_scope/test.js new file mode 100644 index 0000000..65d76a7 --- /dev/null +++ b/tests/js-native-api/test_handle_scope/test.js @@ -0,0 +1,14 @@ +'use strict'; +const testHandleScope = loadAddon('test_handle_scope'); + +testHandleScope.NewScope(); + +assert(testHandleScope.NewScopeEscape() instanceof Object); + +testHandleScope.NewScopeEscapeTwice(); + +assert.throws( + () => { + testHandleScope.NewScopeWithException(() => { throw new RangeError(); }); + }, + RangeError); diff --git a/tests/js-native-api/test_handle_scope/test_handle_scope.c b/tests/js-native-api/test_handle_scope/test_handle_scope.c new file mode 100644 index 0000000..832ce54 --- /dev/null +++ b/tests/js-native-api/test_handle_scope/test_handle_scope.c @@ -0,0 +1,86 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +// these tests validate the handle scope functions in the normal +// flow. Forcing gc behavior to fully validate they are doing +// the right right thing would be quite hard so we keep it +// simple for now. + +static napi_value NewScope(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + napi_value output = NULL; + + NODE_API_CALL(env, napi_open_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +static napi_value NewScopeEscape(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + + NODE_API_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + NODE_API_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return escapee; +} + +static napi_value NewScopeEscapeTwice(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + napi_status status; + + NODE_API_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + status = napi_escape_handle(env, scope, output, &escapee); + NODE_API_ASSERT(env, status == napi_escape_called_twice, "Escaping twice fails"); + NODE_API_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return NULL; +} + +static napi_value NewScopeWithException(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + size_t argc; + napi_value exception_function; + napi_status status; + napi_value output = NULL; + + NODE_API_CALL(env, napi_open_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + + argc = 1; + NODE_API_CALL(env, napi_get_cb_info( + env, info, &argc, &exception_function, NULL, NULL)); + + status = napi_call_function( + env, output, exception_function, 0, NULL, NULL); + NODE_API_ASSERT(env, status == napi_pending_exception, + "Function should have thrown."); + + // Closing a handle scope should still work while an exception is pending. + NODE_API_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("NewScope", NewScope), + DECLARE_NODE_API_PROPERTY("NewScopeEscape", NewScopeEscape), + DECLARE_NODE_API_PROPERTY("NewScopeEscapeTwice", NewScopeEscapeTwice), + DECLARE_NODE_API_PROPERTY("NewScopeWithException", NewScopeWithException), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_instance_data/CMakeLists.txt b/tests/js-native-api/test_instance_data/CMakeLists.txt new file mode 100644 index 0000000..0077b9f --- /dev/null +++ b/tests/js-native-api/test_instance_data/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_instance_data test_instance_data.c) diff --git a/tests/js-native-api/test_instance_data/test.js b/tests/js-native-api/test_instance_data/test.js new file mode 100644 index 0000000..c0e6b9f --- /dev/null +++ b/tests/js-native-api/test_instance_data/test.js @@ -0,0 +1,5 @@ +'use strict'; +const test_instance_data = loadAddon('test_instance_data'); + +// Test that instance data can be accessed from a binding. +assert.strictEqual(test_instance_data.increment(), 42); diff --git a/tests/js-native-api/test_instance_data/test_instance_data.c b/tests/js-native-api/test_instance_data/test_instance_data.c new file mode 100644 index 0000000..5e33ddd --- /dev/null +++ b/tests/js-native-api/test_instance_data/test_instance_data.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +typedef struct { + size_t value; + bool print; + napi_ref js_cb_ref; +} AddonData; + +static napi_value Increment(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL(env, napi_create_uint32(env, ++data->value, &result)); + + return result; +} + +static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { + AddonData* data = raw_data; + if (data->print) { + printf("deleting addon data\n"); + } + if (data->js_cb_ref != NULL) { + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + } + free(data); +} + +static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) { + AddonData* data; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + data->print = true; + + return NULL; +} + +static void TestFinalizer(napi_env env, void* raw_data, void* hint) { + (void) raw_data; + (void) hint; + + AddonData* data; + NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + napi_value js_cb, undefined; + NODE_API_CALL_RETURN_VOID(env, + napi_get_reference_value(env, data->js_cb_ref, &js_cb)); + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(env, + napi_call_function(env, undefined, js_cb, 0, NULL, NULL)); + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + data->js_cb_ref = NULL; +} + +static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result, js_cb; + size_t argc = 1; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL)); + NODE_API_CALL(env, napi_create_object(env, &result)); + NODE_API_CALL(env, + napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL)); + NODE_API_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + AddonData* data = malloc(sizeof(*data)); + data->value = 41; + data->print = false; + data->js_cb_ref = NULL; + + NODE_API_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); + + napi_property_descriptor props[] = { + DECLARE_NODE_API_PROPERTY("increment", Increment), + DECLARE_NODE_API_PROPERTY("setPrintOnDelete", SetPrintOnDelete), + DECLARE_NODE_API_PROPERTY("objectWithFinalizer", ObjectWithFinalizer), + }; + + NODE_API_CALL(env, + napi_define_properties( + env, exports, sizeof(props) / sizeof(*props), props)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_new_target/CMakeLists.txt b/tests/js-native-api/test_new_target/CMakeLists.txt new file mode 100644 index 0000000..897931d --- /dev/null +++ b/tests/js-native-api/test_new_target/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_new_target test_new_target.c) diff --git a/tests/js-native-api/test_new_target/test.js b/tests/js-native-api/test_new_target/test.js new file mode 100644 index 0000000..7d5a4ac --- /dev/null +++ b/tests/js-native-api/test_new_target/test.js @@ -0,0 +1,18 @@ +'use strict'; +const binding = loadAddon('test_new_target'); + +class Class extends binding.BaseClass { + constructor() { + super(); + this.method(); + } + method() { + this.ok = true; + } +} + +assert(new Class() instanceof binding.BaseClass); +assert(new Class().ok); +assert(binding.OrdinaryFunction()); +assert( + new binding.Constructor(binding.Constructor) instanceof binding.Constructor); diff --git a/tests/js-native-api/test_new_target/test_new_target.c b/tests/js-native-api/test_new_target/test_new_target.c new file mode 100644 index 0000000..4e2be97 --- /dev/null +++ b/tests/js-native-api/test_new_target/test_new_target.c @@ -0,0 +1,70 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value BaseClass(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + napi_value thisArg; + NODE_API_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &thisArg, NULL)); + napi_value undefined; + NODE_API_CALL(env, napi_get_undefined(env, &undefined)); + + // this !== new.target since we are being invoked through super() + bool result; + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, thisArg, &result)); + NODE_API_ASSERT(env, !result, "this !== new.target"); + + // new.target !== undefined because we should be called as a new expression + NODE_API_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NODE_API_ASSERT(env, !result, "new.target !== undefined"); + + return thisArg; +} + +static napi_value Constructor(napi_env env, napi_callback_info info) { + bool result; + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + size_t argc = 1; + napi_value argv; + napi_value thisArg; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisArg, NULL)); + napi_value undefined; + NODE_API_CALL(env, napi_get_undefined(env, &undefined)); + + // new.target !== undefined because we should be called as a new expression + NODE_API_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NODE_API_ASSERT(env, !result, "new.target !== undefined"); + + // arguments[0] should be Constructor itself (test harness passed it) + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, argv, &result)); + NODE_API_ASSERT(env, result, "new.target === Constructor"); + + return thisArg; +} + +static napi_value OrdinaryFunction(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + + NODE_API_ASSERT(env, newTargetArg == NULL, "newTargetArg == NULL"); + + napi_value _true; + NODE_API_CALL(env, napi_get_boolean(env, true, &_true)); + return _true; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + const napi_property_descriptor desc[] = { + DECLARE_NODE_API_PROPERTY("BaseClass", BaseClass), + DECLARE_NODE_API_PROPERTY("OrdinaryFunction", OrdinaryFunction), + DECLARE_NODE_API_PROPERTY("Constructor", Constructor) + }; + NODE_API_CALL(env, napi_define_properties(env, exports, 3, desc)); + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_number/CMakeLists.txt b/tests/js-native-api/test_number/CMakeLists.txt new file mode 100644 index 0000000..5e3bdbc --- /dev/null +++ b/tests/js-native-api/test_number/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_number test_number.c test_null.c) diff --git a/tests/js-native-api/test_number/test.js b/tests/js-native-api/test_number/test.js new file mode 100644 index 0000000..333c199 --- /dev/null +++ b/tests/js-native-api/test_number/test.js @@ -0,0 +1,131 @@ +'use strict'; +const test_number = loadAddon('test_number'); + +// Testing api calls for number +function testNumber(num) { + assert.strictEqual(test_number.Test(num), num); +} + +testNumber(0); +testNumber(-0); +testNumber(1); +testNumber(-1); +testNumber(100); +testNumber(2121); +testNumber(-1233); +testNumber(986583); +testNumber(-976675); + +/* eslint-disable no-loss-of-precision */ +testNumber( + 98765432213456789876546896323445679887645323232436587988766545658); +testNumber( + -4350987086545760976737453646576078997096876957864353245245769809); +/* eslint-enable no-loss-of-precision */ +testNumber(Number.MIN_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER + 10); + +testNumber(Number.MIN_VALUE); +testNumber(Number.MAX_VALUE); +testNumber(Number.MAX_VALUE + 10); + +testNumber(Number.POSITIVE_INFINITY); +testNumber(Number.NEGATIVE_INFINITY); +testNumber(Number.NaN); + +function testUint32(input, expected = input) { + assert.strictEqual(expected, test_number.TestUint32Truncation(input)); +} + +// Test zero +testUint32(0.0, 0); +testUint32(-0.0, 0); + +// Test overflow scenarios +testUint32(4294967295); +testUint32(4294967296, 0); +testUint32(4294967297, 1); +testUint32(17 * 4294967296 + 1, 1); +testUint32(-1, 0xffffffff); + +// Validate documented behavior when value is retrieved as 32-bit integer with +// `napi_get_value_int32` +function testInt32(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt32Truncation(input)); +} + +// Test zero +testInt32(0.0, 0); +testInt32(-0.0, 0); + +// Test min/max int32 range +testInt32(-Math.pow(2, 31)); +testInt32(Math.pow(2, 31) - 1); + +// Test overflow scenarios +testInt32(4294967297, 1); +testInt32(4294967296, 0); +testInt32(4294967295, -1); +testInt32(4294967296 * 5 + 3, 3); + +// Test min/max safe integer range +testInt32(Number.MIN_SAFE_INTEGER, 1); +testInt32(Number.MAX_SAFE_INTEGER, -1); + +// Test within int64_t range (with precision loss) +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9) + 1), 1024); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9) + 1), -1024); + +// Test min/max double value +testInt32(-Number.MIN_VALUE, 0); +testInt32(Number.MIN_VALUE, 0); +testInt32(-Number.MAX_VALUE, 0); +testInt32(Number.MAX_VALUE, 0); + +// Test outside int64_t range +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9)), 0); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9)), 0); + +// Test non-finite numbers +testInt32(Number.POSITIVE_INFINITY, 0); +testInt32(Number.NEGATIVE_INFINITY, 0); +testInt32(Number.NaN, 0); + +// Validate documented behavior when value is retrieved as 64-bit integer with +// `napi_get_value_int64` +function testInt64(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt64Truncation(input)); +} + +// Both V8 and ChakraCore return a sentinel value of `0x8000000000000000` when +// the conversion goes out of range, but V8 treats it as unsigned in some cases. +const RANGEERROR_POSITIVE = Math.pow(2, 63); +const RANGEERROR_NEGATIVE = -Math.pow(2, 63); + +// Test zero +testInt64(0.0, 0); +testInt64(-0.0, 0); + +// Test min/max safe integer range +testInt64(Number.MIN_SAFE_INTEGER); +testInt64(Number.MAX_SAFE_INTEGER); + +// Test within int64_t range (with precision loss) +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9) + 1)); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9) + 1)); + +// Test min/max double value +testInt64(-Number.MIN_VALUE, 0); +testInt64(Number.MIN_VALUE, 0); +testInt64(-Number.MAX_VALUE, RANGEERROR_NEGATIVE); +testInt64(Number.MAX_VALUE, RANGEERROR_POSITIVE); + +// Test outside int64_t range +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9)), RANGEERROR_NEGATIVE); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9)), RANGEERROR_POSITIVE); + +// Test non-finite numbers +testInt64(Number.POSITIVE_INFINITY, 0); +testInt64(Number.NEGATIVE_INFINITY, 0); +testInt64(Number.NaN, 0); diff --git a/tests/js-native-api/test_number/test_null.c b/tests/js-native-api/test_number/test_null.c new file mode 100644 index 0000000..37e8b24 --- /dev/null +++ b/tests/js-native-api/test_number/test_null.c @@ -0,0 +1,77 @@ +#include + +#include "../common.h" + +// Unifies the way the macros declare values. +typedef double double_t; + +#define BINDING_FOR_CREATE(initial_capital, lowercase) \ + static napi_value Create##initial_capital(napi_env env, \ + napi_callback_info info) { \ + napi_value return_value, call_result; \ + lowercase##_t value = 42; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + add_returned_status(env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + napi_create_##lowercase(NULL, value, &call_result)); \ + napi_create_##lowercase(env, value, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + return return_value; \ + } + +#define BINDING_FOR_GET_VALUE(initial_capital, lowercase) \ + static napi_value GetValue##initial_capital(napi_env env, \ + napi_callback_info info) { \ + napi_value return_value, call_result; \ + lowercase##_t value = 42; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + NODE_API_CALL(env, napi_create_##lowercase(env, value, &call_result)); \ + add_returned_status( \ + env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + napi_get_value_##lowercase(NULL, call_result, &value)); \ + napi_get_value_##lowercase(env, NULL, &value); \ + add_last_status(env, "valueIsNull", return_value); \ + napi_get_value_##lowercase(env, call_result, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + return return_value; \ + } + +BINDING_FOR_CREATE(Double, double) +BINDING_FOR_CREATE(Int32, int32) +BINDING_FOR_CREATE(Uint32, uint32) +BINDING_FOR_CREATE(Int64, int64) +BINDING_FOR_GET_VALUE(Double, double) +BINDING_FOR_GET_VALUE(Int32, int32) +BINDING_FOR_GET_VALUE(Uint32, uint32) +BINDING_FOR_GET_VALUE(Int64, int64) + +void init_test_null(napi_env env, napi_value exports) { + const napi_property_descriptor test_null_props[] = { + DECLARE_NODE_API_PROPERTY("createDouble", CreateDouble), + DECLARE_NODE_API_PROPERTY("createInt32", CreateInt32), + DECLARE_NODE_API_PROPERTY("createUint32", CreateUint32), + DECLARE_NODE_API_PROPERTY("createInt64", CreateInt64), + DECLARE_NODE_API_PROPERTY("getValueDouble", GetValueDouble), + DECLARE_NODE_API_PROPERTY("getValueInt32", GetValueInt32), + DECLARE_NODE_API_PROPERTY("getValueUint32", GetValueUint32), + DECLARE_NODE_API_PROPERTY("getValueInt64", GetValueInt64), + }; + napi_value test_null; + + NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &test_null)); + NODE_API_CALL_RETURN_VOID( + env, + napi_define_properties(env, + test_null, + sizeof(test_null_props) / sizeof(*test_null_props), + test_null_props)); + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "testNull", test_null)); +} diff --git a/tests/js-native-api/test_number/test_null.h b/tests/js-native-api/test_number/test_null.h new file mode 100644 index 0000000..d5f6bf0 --- /dev/null +++ b/tests/js-native-api/test_number/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ diff --git a/tests/js-native-api/test_number/test_null.js b/tests/js-native-api/test_number/test_null.js new file mode 100644 index 0000000..0439c1b --- /dev/null +++ b/tests/js-native-api/test_number/test_null.js @@ -0,0 +1,16 @@ +'use strict'; +const { testNull } = loadAddon('test_number'); + +const expectedCreateResult = { + envIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}; +const expectedGetValueResult = { + envIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', +}; +[ 'Double', 'Int32', 'Uint32', 'Int64' ].forEach((typeName) => { + assert.deepStrictEqual(testNull['create' + typeName](), expectedCreateResult); + assert.deepStrictEqual(testNull['getValue' + typeName](), expectedGetValueResult); +}); diff --git a/tests/js-native-api/test_number/test_number.c b/tests/js-native-api/test_number/test_number.c new file mode 100644 index 0000000..b816945 --- /dev/null +++ b/tests/js-native-api/test_number/test_number.c @@ -0,0 +1,110 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +static napi_value Test(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + double input; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_double(env, input, &output)); + + return output; +} + +static napi_value TestUint32Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + uint32_t input; + NODE_API_CALL(env, napi_get_value_uint32(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, input, &output)); + + return output; +} + +static napi_value TestInt32Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int32_t input; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_int32(env, input, &output)); + + return output; +} + +static napi_value TestInt64Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int64_t input; + NODE_API_CALL(env, napi_get_value_int64(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_int64(env, input, &output)); + + return output; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("Test", Test), + DECLARE_NODE_API_PROPERTY("TestInt32Truncation", TestInt32Truncation), + DECLARE_NODE_API_PROPERTY("TestUint32Truncation", TestUint32Truncation), + DECLARE_NODE_API_PROPERTY("TestInt64Truncation", TestInt64Truncation), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + init_test_null(env, exports); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_promise/CMakeLists.txt b/tests/js-native-api/test_promise/CMakeLists.txt new file mode 100644 index 0000000..041172e --- /dev/null +++ b/tests/js-native-api/test_promise/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_promise test_promise.c) diff --git a/tests/js-native-api/test_promise/test.js b/tests/js-native-api/test_promise/test.js new file mode 100644 index 0000000..850b96a --- /dev/null +++ b/tests/js-native-api/test_promise/test.js @@ -0,0 +1,72 @@ +'use strict'; +const test_promise = loadAddon('test_promise'); + +const tick = () => new Promise(resolve => setTimeout(resolve, 0)); + +// A resolution +{ + const expected_result = 42; + const promise = test_promise.createPromise(); + let resolved = false; + promise.then((result) => { + assert.strictEqual(result, expected_result); + resolved = true; + }); + test_promise.concludeCurrentPromise(expected_result, true); + await tick(); + assert(resolved, 'resolve callback was not called'); +} + +// A rejection +{ + const expected_result = 'It\'s not you, it\'s me.'; + const promise = test_promise.createPromise(); + let rejected = false; + let thenCalled = false; + promise.then( + () => { throw new Error('unexpected resolve'); }, + (result) => { + assert.strictEqual(result, expected_result); + rejected = true; + }, + ).then(() => { thenCalled = true; }); + test_promise.concludeCurrentPromise(expected_result, false); + await tick(); + assert(rejected, 'reject callback was not called'); + assert(thenCalled, 'then after catch was not called'); +} + +// Chaining +{ + const expected_result = 'chained answer'; + const promise = test_promise.createPromise(); + let resolved = false; + promise.then((result) => { + assert.strictEqual(result, expected_result); + resolved = true; + }); + test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); + await tick(); + assert(resolved, 'chaining resolve callback was not called'); +} + +const promiseTypeTestPromise = test_promise.createPromise(); +assert.strictEqual(test_promise.isPromise(promiseTypeTestPromise), true); +test_promise.concludeCurrentPromise(undefined, true); + +const rejectPromise = Promise.reject(-1); +const expected_reason = -1; +assert.strictEqual(test_promise.isPromise(rejectPromise), true); +let caught = false; +rejectPromise.catch((reason) => { + assert.strictEqual(reason, expected_reason); + caught = true; +}); +await tick(); +assert(caught, 'catch was not called'); + +assert.strictEqual(test_promise.isPromise(2.4), false); +assert.strictEqual(test_promise.isPromise('I promise!'), false); +assert.strictEqual(test_promise.isPromise(undefined), false); +assert.strictEqual(test_promise.isPromise(null), false); +assert.strictEqual(test_promise.isPromise({}), false); diff --git a/tests/js-native-api/test_promise/test_promise.c b/tests/js-native-api/test_promise/test_promise.c new file mode 100644 index 0000000..eef4813 --- /dev/null +++ b/tests/js-native-api/test_promise/test_promise.c @@ -0,0 +1,64 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +napi_deferred deferred = NULL; + +static napi_value createPromise(napi_env env, napi_callback_info info) { + napi_value promise; + + // We do not overwrite an existing deferred. + if (deferred != NULL) { + return NULL; + } + + NODE_API_CALL(env, napi_create_promise(env, &deferred, &promise)); + + return promise; +} + +static napi_value +concludeCurrentPromise(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + bool resolution; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_value_bool(env, argv[1], &resolution)); + if (resolution) { + NODE_API_CALL(env, napi_resolve_deferred(env, deferred, argv[0])); + } else { + NODE_API_CALL(env, napi_reject_deferred(env, deferred, argv[0])); + } + + deferred = NULL; + + return NULL; +} + +static napi_value isPromise(napi_env env, napi_callback_info info) { + napi_value promise, result; + size_t argc = 1; + bool is_promise; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &promise, NULL, NULL)); + NODE_API_CALL(env, napi_is_promise(env, promise, &is_promise)); + NODE_API_CALL(env, napi_get_boolean(env, is_promise, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("createPromise", createPromise), + DECLARE_NODE_API_PROPERTY("concludeCurrentPromise", concludeCurrentPromise), + DECLARE_NODE_API_PROPERTY("isPromise", isPromise), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_properties/CMakeLists.txt b/tests/js-native-api/test_properties/CMakeLists.txt new file mode 100644 index 0000000..df775d5 --- /dev/null +++ b/tests/js-native-api/test_properties/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_properties test_properties.c) diff --git a/tests/js-native-api/test_properties/test.js b/tests/js-native-api/test_properties/test.js new file mode 100644 index 0000000..3767c5d --- /dev/null +++ b/tests/js-native-api/test_properties/test.js @@ -0,0 +1,66 @@ +'use strict'; +const readonlyErrorRE = + /^TypeError: Cannot assign to read only property '.*' of object '#'$/; +const getterOnlyErrorRE = + /^TypeError: Cannot set property .* of # which has only a getter$/; + +// Testing api calls for defining properties +const test_object = loadAddon('test_properties'); + +assert.strictEqual(test_object.echo('hello'), 'hello'); + +test_object.readwriteValue = 1; +assert.strictEqual(test_object.readwriteValue, 1); +test_object.readwriteValue = 2; +assert.strictEqual(test_object.readwriteValue, 2); + +assert.throws(() => { test_object.readonlyValue = 3; }, readonlyErrorRE); + +assert.ok(test_object.hiddenValue); + +// Properties with napi_enumerable attribute should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert(propertyNames.includes('echo')); +assert(propertyNames.includes('readwriteValue')); +assert(propertyNames.includes('readonlyValue')); +assert(!propertyNames.includes('hiddenValue')); +assert(propertyNames.includes('NameKeyValue')); +assert(!propertyNames.includes('readwriteAccessor1')); +assert(!propertyNames.includes('readwriteAccessor2')); +assert(!propertyNames.includes('readonlyAccessor1')); +assert(!propertyNames.includes('readonlyAccessor2')); + +// Validate properties created with symbol +const propertySymbols = Object.getOwnPropertySymbols(test_object); +assert.strictEqual(propertySymbols[0].toString(), 'Symbol(NameKeySymbol)'); +assert.strictEqual(propertySymbols[1].toString(), 'Symbol()'); +assert.strictEqual(propertySymbols[2], Symbol.for('NameKeySymbolFor')); + +// The napi_writable attribute should be ignored for accessors. +const readwriteAccessor1Descriptor = + Object.getOwnPropertyDescriptor(test_object, 'readwriteAccessor1'); +const readonlyAccessor1Descriptor = + Object.getOwnPropertyDescriptor(test_object, 'readonlyAccessor1'); +assert.ok(readwriteAccessor1Descriptor.get != null); +assert.ok(readwriteAccessor1Descriptor.set != null); +assert.strictEqual(readwriteAccessor1Descriptor.value, undefined); +assert.ok(readonlyAccessor1Descriptor.get != null); +assert.strictEqual(readonlyAccessor1Descriptor.set, undefined); +assert.strictEqual(readonlyAccessor1Descriptor.value, undefined); +test_object.readwriteAccessor1 = 1; +assert.strictEqual(test_object.readwriteAccessor1, 1); +assert.strictEqual(test_object.readonlyAccessor1, 1); +assert.throws(() => { test_object.readonlyAccessor1 = 3; }, getterOnlyErrorRE); +test_object.readwriteAccessor2 = 2; +assert.strictEqual(test_object.readwriteAccessor2, 2); +assert.strictEqual(test_object.readonlyAccessor2, 2); +assert.throws(() => { test_object.readonlyAccessor2 = 3; }, getterOnlyErrorRE); + +assert.strictEqual(test_object.hasNamedProperty(test_object, 'echo'), true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'hiddenValue'), + true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'doesnotexist'), + false); diff --git a/tests/js-native-api/test_properties/test_properties.c b/tests/js-native-api/test_properties/test_properties.c new file mode 100644 index 0000000..567dd8c --- /dev/null +++ b/tests/js-native-api/test_properties/test_properties.c @@ -0,0 +1,113 @@ +#define NAPI_VERSION 9 +#include +#include "../common.h" +#include "../entry_point.h" + +static double value_ = 1; + +static napi_value GetValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + return number; +} + +static napi_value SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value_)); + + return NULL; +} + +static napi_value Echo(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + return args[0]; +} + +static napi_value HasNamedProperty(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + // Extract the name of the property to check + char buffer[128]; + size_t copied; + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[1], buffer, sizeof(buffer), &copied)); + + // do the check and create the boolean return value + bool value; + napi_value result; + NODE_API_CALL(env, napi_has_named_property(env, args[0], buffer, &value)); + NODE_API_CALL(env, napi_get_boolean(env, value, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value number; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + napi_value name_value; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "NameKeyValue", NAPI_AUTO_LENGTH, &name_value)); + + napi_value symbol_description; + napi_value name_symbol; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "NameKeySymbol", NAPI_AUTO_LENGTH, &symbol_description)); + NODE_API_CALL(env, + napi_create_symbol(env, symbol_description, &name_symbol)); + + napi_value name_symbol_descriptionless; + NODE_API_CALL(env, + napi_create_symbol(env, NULL, &name_symbol_descriptionless)); + + napi_value name_symbol_for; + NODE_API_CALL(env, node_api_symbol_for(env, + "NameKeySymbolFor", + NAPI_AUTO_LENGTH, + &name_symbol_for)); + + napi_property_descriptor properties[] = { + { "echo", 0, Echo, 0, 0, 0, napi_enumerable, 0 }, + { "readwriteValue", 0, 0, 0, 0, number, napi_enumerable | napi_writable, 0 }, + { "readonlyValue", 0, 0, 0, 0, number, napi_enumerable, 0}, + { "hiddenValue", 0, 0, 0, 0, number, napi_default, 0}, + { NULL, name_value, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol_descriptionless, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol_for, 0, 0, 0, number, napi_enumerable, 0}, + { "readwriteAccessor1", 0, 0, GetValue, SetValue, 0, napi_default, 0}, + { "readwriteAccessor2", 0, 0, GetValue, SetValue, 0, napi_writable, 0}, + { "readonlyAccessor1", 0, 0, GetValue, NULL, 0, napi_default, 0}, + { "readonlyAccessor2", 0, 0, GetValue, NULL, 0, napi_writable, 0}, + { "hasNamedProperty", 0, HasNamedProperty, 0, 0, 0, napi_default, 0 }, + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_reference_double_free/CMakeLists.txt b/tests/js-native-api/test_reference_double_free/CMakeLists.txt new file mode 100644 index 0000000..2d70b2b --- /dev/null +++ b/tests/js-native-api/test_reference_double_free/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_reference_double_free test_reference_double_free.c) diff --git a/tests/js-native-api/test_reference_double_free/test.js b/tests/js-native-api/test_reference_double_free/test.js new file mode 100644 index 0000000..13d411b --- /dev/null +++ b/tests/js-native-api/test_reference_double_free/test.js @@ -0,0 +1,9 @@ +'use strict'; + +// This test makes no assertions. It tests a fix without which it will crash +// with a double free. + +const addon = loadAddon('test_reference_double_free'); + +{ new addon.MyObject(true); } +{ new addon.MyObject(false); } diff --git a/tests/js-native-api/test_reference_double_free/test_reference_double_free.c b/tests/js-native-api/test_reference_double_free/test_reference_double_free.c new file mode 100644 index 0000000..0e0f91c --- /dev/null +++ b/tests/js-native-api/test_reference_double_free/test_reference_double_free.c @@ -0,0 +1,90 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static size_t g_call_count = 0; + +static void Destructor(napi_env env, void* data, void* nothing) { + napi_ref* ref = data; + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref)); + free(ref); +} + +static void NoDeleteDestructor(napi_env env, void* data, void* hint) { + napi_ref* ref = data; + size_t* call_count = hint; + + // This destructor must be called exactly once. + if ((*call_count) > 0) abort(); + *call_count = ((*call_count) + 1); + free(ref); +} + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_this, js_delete; + bool delete; + napi_ref* ref = malloc(sizeof(*ref)); + + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, &js_delete, &js_this, NULL)); + NODE_API_CALL(env, napi_get_value_bool(env, js_delete, &delete)); + + if (delete) { + NODE_API_CALL(env, + napi_wrap(env, js_this, ref, Destructor, NULL, ref)); + } else { + NODE_API_CALL(env, + napi_wrap(env, js_this, ref, NoDeleteDestructor, &g_call_count, ref)); + } + NODE_API_CALL(env, napi_reference_ref(env, *ref, NULL)); + + return js_this; +} + +static void NoopDeleter(napi_env env, void* data, void* hint) {} + +// Tests that calling napi_remove_wrap and napi_delete_reference consecutively +// doesn't crash the process. +// This is analogous to the test https://github.com/nodejs/node-addon-api/blob/main/test/objectwrap_constructor_exception.cc. +// In which the Napi::ObjectWrap<> is being destructed immediately after napi_wrap. +// As Napi::ObjectWrap<> is a subclass of Napi::Reference<>, napi_remove_wrap +// in the destructor of Napi::ObjectWrap<> is called before napi_delete_reference +// in the destructor of Napi::Reference<>. +static napi_value DeleteImmediately(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_obj; + napi_ref ref; + napi_valuetype type; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_obj, NULL, NULL)); + + NODE_API_CALL(env, napi_typeof(env, js_obj, &type)); + NODE_API_ASSERT(env, type == napi_object, "Expected object parameter"); + + NODE_API_CALL(env, napi_wrap(env, js_obj, NULL, NoopDeleter, NULL, &ref)); + NODE_API_CALL(env, napi_remove_wrap(env, js_obj, NULL)); + NODE_API_CALL(env, napi_delete_reference(env, ref)); + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value myobj_ctor; + NODE_API_CALL(env, + napi_define_class( + env, "MyObject", NAPI_AUTO_LENGTH, New, NULL, 0, NULL, &myobj_ctor)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "MyObject", myobj_ctor)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("deleteImmediately", DeleteImmediately), + }; + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/test_reference_double_free/test_wrap.js b/tests/js-native-api/test_reference_double_free/test_wrap.js new file mode 100644 index 0000000..fece5de --- /dev/null +++ b/tests/js-native-api/test_reference_double_free/test_wrap.js @@ -0,0 +1,8 @@ +'use strict'; + +// This test makes no assertions. It tests that calling napi_remove_wrap and +// napi_delete_reference consecutively doesn't crash the process. + +const addon = loadAddon('test_reference_double_free'); + +addon.deleteImmediately({}); diff --git a/tests/js-native-api/test_symbol/CMakeLists.txt b/tests/js-native-api/test_symbol/CMakeLists.txt new file mode 100644 index 0000000..2bf0ef8 --- /dev/null +++ b/tests/js-native-api/test_symbol/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_symbol test_symbol.c) diff --git a/tests/js-native-api/test_symbol/test1.js b/tests/js-native-api/test_symbol/test1.js new file mode 100644 index 0000000..c6dd5e5 --- /dev/null +++ b/tests/js-native-api/test_symbol/test1.js @@ -0,0 +1,15 @@ +'use strict'; +const test_symbol = loadAddon('test_symbol'); + +const sym = test_symbol.New('test'); +assert.strictEqual(sym.toString(), 'Symbol(test)'); + +const myObj = {}; +const fooSym = test_symbol.New('foo'); +const otherSym = test_symbol.New('bar'); +myObj.foo = 'bar'; +myObj[fooSym] = 'baz'; +myObj[otherSym] = 'bing'; +assert.strictEqual(myObj.foo, 'bar'); +assert.strictEqual(myObj[fooSym], 'baz'); +assert.strictEqual(myObj[otherSym], 'bing'); diff --git a/tests/js-native-api/test_symbol/test2.js b/tests/js-native-api/test_symbol/test2.js new file mode 100644 index 0000000..208de02 --- /dev/null +++ b/tests/js-native-api/test_symbol/test2.js @@ -0,0 +1,13 @@ +'use strict'; +const test_symbol = loadAddon('test_symbol'); + +const fooSym = test_symbol.New('foo'); +assert.strictEqual(fooSym.toString(), 'Symbol(foo)'); + +const myObj = {}; +myObj.foo = 'bar'; +myObj[fooSym] = 'baz'; + +assert.deepStrictEqual(Object.keys(myObj), ['foo']); +assert.deepStrictEqual(Object.getOwnPropertyNames(myObj), ['foo']); +assert.deepStrictEqual(Object.getOwnPropertySymbols(myObj), [fooSym]); diff --git a/tests/js-native-api/test_symbol/test3.js b/tests/js-native-api/test_symbol/test3.js new file mode 100644 index 0000000..5d4e15b --- /dev/null +++ b/tests/js-native-api/test_symbol/test3.js @@ -0,0 +1,15 @@ +'use strict'; +const test_symbol = loadAddon('test_symbol'); + +assert.notStrictEqual(test_symbol.New(), test_symbol.New()); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('foo')); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('bar')); + +const foo1 = test_symbol.New('foo'); +const foo2 = test_symbol.New('foo'); +const object = { + [foo1]: 1, + [foo2]: 2, +}; +assert.strictEqual(object[foo1], 1); +assert.strictEqual(object[foo2], 2); diff --git a/tests/js-native-api/test_symbol/test_symbol.c b/tests/js-native-api/test_symbol/test_symbol.c new file mode 100644 index 0000000..b146582 --- /dev/null +++ b/tests/js-native-api/test_symbol/test_symbol.c @@ -0,0 +1,38 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value description = NULL; + if (argc >= 1) { + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NODE_API_ASSERT(env, valuetype == napi_string, + "Wrong type of arguments. Expects a string."); + + description = args[0]; + } + + napi_value symbol; + NODE_API_CALL(env, napi_create_symbol(env, description, &symbol)); + + return symbol; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("New", New), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END From da944a32c166c60984d1bc728e7ddba336cec228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 28 Feb 2026 14:28:59 +0100 Subject: [PATCH 2/8] doc: update PORTING.md with ported status and experimental API notes Mark 17 easy js-native-api tests as Ported (test_dataview as Partial due to missing experimental SharedArrayBuffer APIs in node-api-headers). Add "Experimental Node-API Features" section documenting which tests depend on experimental APIs not yet available in node-api-headers, and the approach needed to support them. References #26. Co-Authored-By: Claude Opus 4.6 --- PORTING.md | 163 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 96 insertions(+), 67 deletions(-) diff --git a/PORTING.md b/PORTING.md index 834d704..205703d 100644 --- a/PORTING.md +++ b/PORTING.md @@ -23,87 +23,115 @@ in `js_native_api.h` and is therefore engine-agnostic. ## Difficulty Ratings Difficulty is assessed on two axes: + - **Size/complexity** — total lines of C/C++ and JS across all source files - **Runtime-API dependence** — pure `js_native_api.h` is cheapest; Node.js extensions and direct libuv calls require harness work or Node-only scoping -| Rating | Meaning | -|---|---| -| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port | -| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract | -| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping | +| Rating | Meaning | +| ------ | -------------------------------------------------------------------------------------------------------- | +| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port | +| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract | +| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping | ## Engine-specific (`js-native-api`) Tests covering the engine-specific part of Node-API, defined in `js_native_api.h`. -| Directory | Status | Difficulty | -|---|---|---| -| `2_function_arguments` | Ported | — | -| `3_callbacks` | Not ported | Easy | -| `4_object_factory` | Not ported | Easy | -| `5_function_factory` | Not ported | Easy | -| `6_object_wrap` | Not ported | Medium | -| `7_factory_wrap` | Not ported | Easy | -| `8_passing_wrapped` | Not ported | Easy | -| `test_array` | Not ported | Easy | -| `test_bigint` | Not ported | Easy | -| `test_cannot_run_js` | Not ported | Medium | -| `test_constructor` | Not ported | Medium | -| `test_conversions` | Not ported | Medium | -| `test_dataview` | Not ported | Easy | -| `test_date` | Not ported | Easy | -| `test_error` | Not ported | Medium | -| `test_exception` | Not ported | Medium | -| `test_finalizer` | Not ported | Medium | -| `test_function` | Not ported | Medium | -| `test_general` | Not ported | Hard | -| `test_handle_scope` | Not ported | Easy | -| `test_instance_data` | Not ported | Easy | -| `test_new_target` | Not ported | Easy | -| `test_number` | Not ported | Easy | -| `test_object` | Not ported | Hard | -| `test_promise` | Not ported | Easy | -| `test_properties` | Not ported | Easy | -| `test_reference` | Not ported | Medium | -| `test_reference_double_free` | Not ported | Easy | -| `test_sharedarraybuffer` | Not ported | Medium | -| `test_string` | Not ported | Medium | -| `test_symbol` | Not ported | Easy | -| `test_typedarray` | Not ported | Medium | +| Directory | Status | Difficulty | +| ---------------------------- | ---------- | ---------- | +| `2_function_arguments` | Ported | — | +| `3_callbacks` | Ported | Easy | +| `4_object_factory` | Ported | Easy | +| `5_function_factory` | Ported | Easy | +| `6_object_wrap` | Not ported | Medium | +| `7_factory_wrap` | Ported | Easy | +| `8_passing_wrapped` | Ported | Easy | +| `test_array` | Ported | Easy | +| `test_bigint` | Ported | Easy | +| `test_cannot_run_js` | Not ported | Medium | +| `test_constructor` | Not ported | Medium | +| `test_conversions` | Not ported | Medium | +| `test_dataview` | Partial | Easy | +| `test_date` | Ported | Easy | +| `test_error` | Not ported | Medium | +| `test_exception` | Not ported | Medium | +| `test_finalizer` | Not ported | Medium | +| `test_function` | Not ported | Medium | +| `test_general` | Not ported | Hard | +| `test_handle_scope` | Ported | Easy | +| `test_instance_data` | Ported | Easy | +| `test_new_target` | Ported | Easy | +| `test_number` | Ported | Easy | +| `test_object` | Not ported | Hard | +| `test_promise` | Ported | Easy | +| `test_properties` | Ported | Easy | +| `test_reference` | Not ported | Medium | +| `test_reference_double_free` | Ported | Easy | +| `test_sharedarraybuffer` | Not ported | Medium | +| `test_string` | Not ported | Medium | +| `test_symbol` | Ported | Easy | +| `test_typedarray` | Not ported | Medium | ## Runtime-specific (`node-api`) Tests covering the runtime-specific part of Node-API, defined in `node_api.h`. -| Directory | Status | Difficulty | -|---|---|---| -| `1_hello_world` | Not ported | Easy | -| `test_async` | Not ported | Hard | -| `test_async_cleanup_hook` | Not ported | Hard | -| `test_async_context` | Not ported | Hard | -| `test_buffer` | Not ported | Medium | -| `test_callback_scope` | Not ported | Hard | -| `test_cleanup_hook` | Not ported | Medium | -| `test_env_teardown_gc` | Not ported | Easy | -| `test_exception` | Not ported | Easy | -| `test_fatal` | Not ported | Hard | -| `test_fatal_exception` | Not ported | Easy | -| `test_general` | Not ported | Medium | -| `test_init_order` | Not ported | Medium | -| `test_instance_data` | Not ported | Hard | -| `test_make_callback` | Not ported | Hard | -| `test_make_callback_recurse` | Not ported | Hard | -| `test_null_init` | Not ported | Medium | -| `test_reference_by_node_api_version` | Not ported | Medium | -| `test_sea_addon` | Not ported | Hard | -| `test_threadsafe_function` | Not ported | Hard | -| `test_threadsafe_function_shutdown` | Not ported | Hard | -| `test_uv_loop` | Not ported | Hard | -| `test_uv_threadpool_size` | Not ported | Hard | -| `test_worker_buffer_callback` | Not ported | Hard | -| `test_worker_terminate` | Not ported | Hard | -| `test_worker_terminate_finalization` | Not ported | Hard | +| Directory | Status | Difficulty | +| ------------------------------------ | ---------- | ---------- | +| `1_hello_world` | Not ported | Easy | +| `test_async` | Not ported | Hard | +| `test_async_cleanup_hook` | Not ported | Hard | +| `test_async_context` | Not ported | Hard | +| `test_buffer` | Not ported | Medium | +| `test_callback_scope` | Not ported | Hard | +| `test_cleanup_hook` | Not ported | Medium | +| `test_env_teardown_gc` | Not ported | Easy | +| `test_exception` | Not ported | Easy | +| `test_fatal` | Not ported | Hard | +| `test_fatal_exception` | Not ported | Easy | +| `test_general` | Not ported | Medium | +| `test_init_order` | Not ported | Medium | +| `test_instance_data` | Not ported | Hard | +| `test_make_callback` | Not ported | Hard | +| `test_make_callback_recurse` | Not ported | Hard | +| `test_null_init` | Not ported | Medium | +| `test_reference_by_node_api_version` | Not ported | Medium | +| `test_sea_addon` | Not ported | Hard | +| `test_threadsafe_function` | Not ported | Hard | +| `test_threadsafe_function_shutdown` | Not ported | Hard | +| `test_uv_loop` | Not ported | Hard | +| `test_uv_threadpool_size` | Not ported | Hard | +| `test_worker_buffer_callback` | Not ported | Hard | +| `test_worker_terminate` | Not ported | Hard | +| `test_worker_terminate_finalization` | Not ported | Hard | + +## Experimental Node-API Features + +Several tests in the upstream Node.js repository use experimental APIs that are guarded behind +`#ifdef NAPI_EXPERIMENTAL` in Node.js's `js_native_api.h`. The `node-api-headers` package (which +the CTS uses for compilation) does not currently include any of these experimental API declarations. + +When `NAPI_EXPERIMENTAL` is defined in Node.js, `NAPI_VERSION` is set to +`NAPI_VERSION_EXPERIMENTAL (2147483647)`. The `NAPI_MODULE` macro exports this version, and the +runtime uses it to decide whether to enable experimental behavior for that addon. + +| Feature macro | APIs | Used by | +| --------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------- | +| `NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER` | `node_api_is_sharedarraybuffer`, `node_api_create_sharedarraybuffer` | `test_dataview`, `test_sharedarraybuffer` | +| `NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES` | `node_api_create_object_with_properties` | `test_object` | +| `NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE` | `node_api_set_prototype` | `test_general` | +| `NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER` | `node_api_post_finalizer` | `test_general`, `test_finalizer`, `6_object_wrap` | + +Tests that depend on these APIs are currently ported without the experimental test cases (marked +as "Partial" in the status column) or not ported at all. To fully support them, the CTS will need: + +1. A mechanism for compiling addons with experimental API access (either from updated + `node-api-headers`, CTS-provided forward declarations or copies of headers from the Node.js main repository) +2. A way for implementors to declare which experimental features their runtime supports +3. Conditional test execution that skips experimental assertions on runtimes that don't support them + +See [#26](https://github.com/nodejs/node-api-cts/issues/26) for the full design discussion. ## Special Considerations @@ -144,6 +172,7 @@ The following tests call into libuv directly — `napi_get_uv_event_loop`, `uv_t - `test_uv_loop`, `test_uv_threadpool_size` Porting options: + 1. **Node-only scope** — mark these tests as Node.js-only and skip on other runtimes. 2. **Harness abstraction** — introduce a minimal platform-agnostic threading/async API in the harness (e.g., `cts_thread_create`, `cts_async_schedule`) that implementors back with their From d8c722969ab38f6a25db40fee4f1aae2c7ffcdc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 1 Mar 2026 09:36:45 +0100 Subject: [PATCH 3/8] Cleaning up the porting docs --- PORTING.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/PORTING.md b/PORTING.md index 205703d..dacd139 100644 --- a/PORTING.md +++ b/PORTING.md @@ -40,37 +40,37 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | Directory | Status | Difficulty | | ---------------------------- | ---------- | ---------- | -| `2_function_arguments` | Ported | — | -| `3_callbacks` | Ported | Easy | -| `4_object_factory` | Ported | Easy | -| `5_function_factory` | Ported | Easy | +| `2_function_arguments` | Ported ✅ | — | +| `3_callbacks` | Ported ✅ | Easy | +| `4_object_factory` | Ported ✅ | Easy | +| `5_function_factory` | Ported ✅ | Easy | | `6_object_wrap` | Not ported | Medium | -| `7_factory_wrap` | Ported | Easy | -| `8_passing_wrapped` | Ported | Easy | -| `test_array` | Ported | Easy | -| `test_bigint` | Ported | Easy | +| `7_factory_wrap` | Ported ✅ | Easy | +| `8_passing_wrapped` | Ported ✅ | Easy | +| `test_array` | Ported ✅ | Easy | +| `test_bigint` | Ported ✅ | Easy | | `test_cannot_run_js` | Not ported | Medium | | `test_constructor` | Not ported | Medium | | `test_conversions` | Not ported | Medium | | `test_dataview` | Partial | Easy | -| `test_date` | Ported | Easy | +| `test_date` | Ported ✅ | Easy | | `test_error` | Not ported | Medium | | `test_exception` | Not ported | Medium | | `test_finalizer` | Not ported | Medium | | `test_function` | Not ported | Medium | | `test_general` | Not ported | Hard | -| `test_handle_scope` | Ported | Easy | -| `test_instance_data` | Ported | Easy | -| `test_new_target` | Ported | Easy | -| `test_number` | Ported | Easy | +| `test_handle_scope` | Ported ✅ | Easy | +| `test_instance_data` | Ported ✅ | Easy | +| `test_new_target` | Ported ✅ | Easy | +| `test_number` | Ported ✅ | Easy | | `test_object` | Not ported | Hard | -| `test_promise` | Ported | Easy | -| `test_properties` | Ported | Easy | +| `test_promise` | Ported ✅ | Easy | +| `test_properties` | Ported ✅ | Easy | | `test_reference` | Not ported | Medium | -| `test_reference_double_free` | Ported | Easy | +| `test_reference_double_free` | Ported ✅ | Easy | | `test_sharedarraybuffer` | Not ported | Medium | | `test_string` | Not ported | Medium | -| `test_symbol` | Ported | Easy | +| `test_symbol` | Ported ✅ | Easy | | `test_typedarray` | Not ported | Medium | ## Runtime-specific (`node-api`) From 02b384bb4c74f7edd93e62a2de7a73e903a4185d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 7 Mar 2026 15:00:56 +0100 Subject: [PATCH 4/8] Remove partial ported dataview test --- PORTING.md | 2 +- .../test_dataview/CMakeLists.txt | 1 - tests/js-native-api/test_dataview/test.js | 21 ---- .../test_dataview/test_dataview.c | 104 ------------------ 4 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 tests/js-native-api/test_dataview/CMakeLists.txt delete mode 100644 tests/js-native-api/test_dataview/test.js delete mode 100644 tests/js-native-api/test_dataview/test_dataview.c diff --git a/PORTING.md b/PORTING.md index dacd139..559ce95 100644 --- a/PORTING.md +++ b/PORTING.md @@ -52,7 +52,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_cannot_run_js` | Not ported | Medium | | `test_constructor` | Not ported | Medium | | `test_conversions` | Not ported | Medium | -| `test_dataview` | Partial | Easy | +| `test_dataview` | Not ported | Medium | | `test_date` | Ported ✅ | Easy | | `test_error` | Not ported | Medium | | `test_exception` | Not ported | Medium | diff --git a/tests/js-native-api/test_dataview/CMakeLists.txt b/tests/js-native-api/test_dataview/CMakeLists.txt deleted file mode 100644 index 4411f2e..0000000 --- a/tests/js-native-api/test_dataview/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_node_api_cts_addon(test_dataview test_dataview.c) diff --git a/tests/js-native-api/test_dataview/test.js b/tests/js-native-api/test_dataview/test.js deleted file mode 100644 index c267919..0000000 --- a/tests/js-native-api/test_dataview/test.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -const test_dataview = loadAddon('test_dataview'); - -// Test for creating dataview with ArrayBuffer -{ - const buffer = new ArrayBuffer(128); - const template = Reflect.construct(DataView, [buffer]); - - const theDataview = test_dataview.CreateDataViewFromJSDataView(template); - assert(theDataview instanceof DataView, - `Expect ${theDataview} to be a DataView`); -} - -// Test for creating dataview with ArrayBuffer and invalid range -{ - const buffer = new ArrayBuffer(128); - assert.throws(() => { - test_dataview.CreateDataView(buffer, 10, 200); - }, RangeError); -} - diff --git a/tests/js-native-api/test_dataview/test_dataview.c b/tests/js-native-api/test_dataview/test_dataview.c deleted file mode 100644 index ff774fa..0000000 --- a/tests/js-native-api/test_dataview/test_dataview.c +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include -#include "../common.h" -#include "../entry_point.h" - -static napi_value CreateDataView(napi_env env, napi_callback_info info) { - size_t argc = 3; - napi_value args [3]; - NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); - - NODE_API_ASSERT(env, argc == 3, "Wrong number of arguments"); - - napi_valuetype valuetype0; - napi_value arraybuffer = args[0]; - - NODE_API_CALL(env, napi_typeof(env, arraybuffer, &valuetype0)); - NODE_API_ASSERT(env, valuetype0 == napi_object, - "Wrong type of arguments. Expects a ArrayBuffer as the first " - "argument."); - - bool is_arraybuffer; - NODE_API_CALL(env, napi_is_arraybuffer(env, arraybuffer, &is_arraybuffer)); - - NODE_API_ASSERT(env, - is_arraybuffer, - "Wrong type of arguments. Expects an ArrayBuffer as the " - "first argument."); - - napi_valuetype valuetype1; - NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); - - NODE_API_ASSERT(env, valuetype1 == napi_number, - "Wrong type of arguments. Expects a number as second argument."); - - size_t byte_offset = 0; - NODE_API_CALL(env, napi_get_value_uint32(env, args[1], (uint32_t*)(&byte_offset))); - - napi_valuetype valuetype2; - NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype2)); - - NODE_API_ASSERT(env, valuetype2 == napi_number, - "Wrong type of arguments. Expects a number as third argument."); - - size_t length = 0; - NODE_API_CALL(env, napi_get_value_uint32(env, args[2], (uint32_t*)(&length))); - - napi_value output_dataview; - NODE_API_CALL(env, - napi_create_dataview(env, length, arraybuffer, - byte_offset, &output_dataview)); - - return output_dataview; -} - -static napi_value CreateDataViewFromJSDataView(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value args [1]; - NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); - - NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); - - napi_valuetype valuetype; - napi_value input_dataview = args[0]; - - NODE_API_CALL(env, napi_typeof(env, input_dataview, &valuetype)); - NODE_API_ASSERT(env, valuetype == napi_object, - "Wrong type of arguments. Expects a DataView as the first " - "argument."); - - bool is_dataview; - NODE_API_CALL(env, napi_is_dataview(env, input_dataview, &is_dataview)); - NODE_API_ASSERT(env, is_dataview, - "Wrong type of arguments. Expects a DataView as the first " - "argument."); - size_t byte_offset = 0; - size_t length = 0; - napi_value buffer; - NODE_API_CALL(env, - napi_get_dataview_info(env, input_dataview, &length, NULL, - &buffer, &byte_offset)); - - napi_value output_dataview; - NODE_API_CALL(env, - napi_create_dataview(env, length, buffer, - byte_offset, &output_dataview)); - - - return output_dataview; -} - -EXTERN_C_START -napi_value Init(napi_env env, napi_value exports) { - napi_property_descriptor descriptors[] = { - DECLARE_NODE_API_PROPERTY("CreateDataView", CreateDataView), - DECLARE_NODE_API_PROPERTY("CreateDataViewFromJSDataView", - CreateDataViewFromJSDataView) - }; - - NODE_API_CALL(env, napi_define_properties( - env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); - - return exports; -} -EXTERN_C_END From ba80ae09a406e25da7a3e71b29f0add370311476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 7 Mar 2026 15:56:15 +0100 Subject: [PATCH 5/8] Add must-call harness helper --- implementors/node/must-call.js | 36 ++++++++++++++++ implementors/node/tests.ts | 8 ++++ tests/harness/must-call.js | 77 ++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 implementors/node/must-call.js create mode 100644 tests/harness/must-call.js diff --git a/implementors/node/must-call.js b/implementors/node/must-call.js new file mode 100644 index 0000000..592357b --- /dev/null +++ b/implementors/node/must-call.js @@ -0,0 +1,36 @@ +/** + * Wraps a function and returns a [wrapper, called] tuple. + * - `wrapper` — call this in place of the original function + * - `called` — a Promise that resolves (with the return value of fn) once + * wrapper has been invoked + * + * If `fn` is omitted, a no-op function is used. + * + * Usage: + * const [onResolve, resolved] = mustCall((result) => { + * assert.strictEqual(result, 42); + * }); + * promise.then(onResolve); + * await resolved; + */ +const mustCall = (fn) => { + let resolve; + const called = new Promise((r) => { resolve = r; }); + const wrapper = (...args) => { + const result = fn ? fn(...args) : undefined; + resolve(result); + return result; + }; + return [wrapper, called]; +}; + +/** + * Returns a function that throws immediately if called. + */ +const mustNotCall = (msg) => { + return () => { + throw new Error(msg || "mustNotCall function was called"); + }; +}; + +Object.assign(globalThis, { mustCall, mustNotCall }); diff --git a/implementors/node/tests.ts b/implementors/node/tests.ts index d39c64b..d5bafb0 100644 --- a/implementors/node/tests.ts +++ b/implementors/node/tests.ts @@ -28,6 +28,12 @@ const GC_MODULE_PATH = path.join( "node", "gc.js" ); +const MUST_CALL_MODULE_PATH = path.join( + ROOT_PATH, + "implementors", + "node", + "must-call.js" +); export function listDirectoryEntries(dir: string) { const entries = fs.readdirSync(dir, { withFileTypes: true }); @@ -64,6 +70,8 @@ export function runFileInSubprocess( "file://" + LOAD_ADDON_MODULE_PATH, "--import", "file://" + GC_MODULE_PATH, + "--import", + "file://" + MUST_CALL_MODULE_PATH, filePath, ], { cwd } diff --git a/tests/harness/must-call.js b/tests/harness/must-call.js new file mode 100644 index 0000000..5e2cb43 --- /dev/null +++ b/tests/harness/must-call.js @@ -0,0 +1,77 @@ +// mustCall is a function +if (typeof mustCall !== 'function') { + throw new Error('Expected a global mustCall function'); +} + +// mustCall returns a [wrapper, called] tuple +{ + const [wrapper, called] = mustCall(); + if (typeof wrapper !== 'function') { + throw new Error('mustCall()[0] must be a function'); + } + if (!(called instanceof Promise)) { + throw new Error('mustCall()[1] must be a Promise'); + } + wrapper(); + await called; +} + +// mustCall forwards arguments and return value +{ + const [wrapper, called] = mustCall((a, b) => a + b); + const result = wrapper(2, 3); + assert.strictEqual(result, 5); + const resolvedValue = await called; + assert.strictEqual(resolvedValue, 5); +} + +// mustCall without fn argument works as a no-op wrapper +{ + const [wrapper, called] = mustCall(); + const result = wrapper('ignored'); + assert.strictEqual(result, undefined); + await called; +} + +// mustNotCall is a function +if (typeof mustNotCall !== 'function') { + throw new Error('Expected a global mustNotCall function'); +} + +// mustNotCall returns a function +{ + const fn = mustNotCall(); + if (typeof fn !== 'function') { + throw new Error('mustNotCall() must return a function'); + } +} + +// mustNotCall() throws when called +{ + const fn = mustNotCall(); + let threw = false; + try { + fn(); + } catch { + threw = true; + } + if (!threw) throw new Error('mustNotCall() must throw when called'); +} + +// mustNotCall(msg) includes the message +{ + const fn = mustNotCall('custom message'); + let threw = false; + try { + fn(); + } catch (error) { + threw = true; + if (!(error instanceof Error)) { + throw new Error('mustNotCall must throw an Error instance'); + } + if (!error.message.includes('custom message')) { + throw new Error(`mustNotCall error must include custom message, got: "${error.message}"`); + } + } + if (!threw) throw new Error('mustNotCall(msg) must throw when called'); +} From deeea0911eeaadeed43dc1f49f05124f6a0bf10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 7 Mar 2026 15:56:28 +0100 Subject: [PATCH 6/8] Use mustCall from harness --- tests/js-native-api/test_promise/test.js | 44 ++++++++---------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/tests/js-native-api/test_promise/test.js b/tests/js-native-api/test_promise/test.js index 850b96a..627cac8 100644 --- a/tests/js-native-api/test_promise/test.js +++ b/tests/js-native-api/test_promise/test.js @@ -1,53 +1,41 @@ 'use strict'; const test_promise = loadAddon('test_promise'); -const tick = () => new Promise(resolve => setTimeout(resolve, 0)); - // A resolution { const expected_result = 42; const promise = test_promise.createPromise(); - let resolved = false; - promise.then((result) => { + const [onResolve, resolved] = mustCall((result) => { assert.strictEqual(result, expected_result); - resolved = true; }); + promise.then(onResolve); test_promise.concludeCurrentPromise(expected_result, true); - await tick(); - assert(resolved, 'resolve callback was not called'); + await resolved; } // A rejection { const expected_result = 'It\'s not you, it\'s me.'; const promise = test_promise.createPromise(); - let rejected = false; - let thenCalled = false; - promise.then( - () => { throw new Error('unexpected resolve'); }, - (result) => { - assert.strictEqual(result, expected_result); - rejected = true; - }, - ).then(() => { thenCalled = true; }); + const [onReject, rejected] = mustCall((result) => { + assert.strictEqual(result, expected_result); + }); + const [onThen, thenCalled] = mustCall(); + promise.then(mustNotCall(), onReject).then(onThen); test_promise.concludeCurrentPromise(expected_result, false); - await tick(); - assert(rejected, 'reject callback was not called'); - assert(thenCalled, 'then after catch was not called'); + await thenCalled; } // Chaining { const expected_result = 'chained answer'; const promise = test_promise.createPromise(); - let resolved = false; - promise.then((result) => { + const [onResolve, resolved] = mustCall((result) => { assert.strictEqual(result, expected_result); - resolved = true; }); + promise.then(onResolve); test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); - await tick(); - assert(resolved, 'chaining resolve callback was not called'); + await resolved; } const promiseTypeTestPromise = test_promise.createPromise(); @@ -57,13 +45,11 @@ test_promise.concludeCurrentPromise(undefined, true); const rejectPromise = Promise.reject(-1); const expected_reason = -1; assert.strictEqual(test_promise.isPromise(rejectPromise), true); -let caught = false; -rejectPromise.catch((reason) => { +const [onCatch, caught] = mustCall((reason) => { assert.strictEqual(reason, expected_reason); - caught = true; }); -await tick(); -assert(caught, 'catch was not called'); +rejectPromise.catch(onCatch); +await caught; assert.strictEqual(test_promise.isPromise(2.4), false); assert.strictEqual(test_promise.isPromise('I promise!'), false); From 0f6e412ec1bc2d88bd78cc22b6a4de55e74e7088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 7 Mar 2026 16:20:14 +0100 Subject: [PATCH 7/8] Remove simplified test_instance_data port The upstream test verifies finalizer and environment teardown behavior via subprocesses and worker threads, not just increment(). The simplified CTS port lost that coverage, so remove it and reclassify as Medium. Co-Authored-By: Claude Opus 4.6 --- PORTING.md | 2 +- .../test_instance_data/CMakeLists.txt | 1 - .../js-native-api/test_instance_data/test.js | 5 - .../test_instance_data/test_instance_data.c | 96 ------------------- 4 files changed, 1 insertion(+), 103 deletions(-) delete mode 100644 tests/js-native-api/test_instance_data/CMakeLists.txt delete mode 100644 tests/js-native-api/test_instance_data/test.js delete mode 100644 tests/js-native-api/test_instance_data/test_instance_data.c diff --git a/PORTING.md b/PORTING.md index 559ce95..e303d1f 100644 --- a/PORTING.md +++ b/PORTING.md @@ -60,7 +60,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_function` | Not ported | Medium | | `test_general` | Not ported | Hard | | `test_handle_scope` | Ported ✅ | Easy | -| `test_instance_data` | Ported ✅ | Easy | +| `test_instance_data` | Not ported | Medium | | `test_new_target` | Ported ✅ | Easy | | `test_number` | Ported ✅ | Easy | | `test_object` | Not ported | Hard | diff --git a/tests/js-native-api/test_instance_data/CMakeLists.txt b/tests/js-native-api/test_instance_data/CMakeLists.txt deleted file mode 100644 index 0077b9f..0000000 --- a/tests/js-native-api/test_instance_data/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_node_api_cts_addon(test_instance_data test_instance_data.c) diff --git a/tests/js-native-api/test_instance_data/test.js b/tests/js-native-api/test_instance_data/test.js deleted file mode 100644 index c0e6b9f..0000000 --- a/tests/js-native-api/test_instance_data/test.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; -const test_instance_data = loadAddon('test_instance_data'); - -// Test that instance data can be accessed from a binding. -assert.strictEqual(test_instance_data.increment(), 42); diff --git a/tests/js-native-api/test_instance_data/test_instance_data.c b/tests/js-native-api/test_instance_data/test_instance_data.c deleted file mode 100644 index 5e33ddd..0000000 --- a/tests/js-native-api/test_instance_data/test_instance_data.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include "../common.h" -#include "../entry_point.h" - -typedef struct { - size_t value; - bool print; - napi_ref js_cb_ref; -} AddonData; - -static napi_value Increment(napi_env env, napi_callback_info info) { - AddonData* data; - napi_value result; - - NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); - NODE_API_CALL(env, napi_create_uint32(env, ++data->value, &result)); - - return result; -} - -static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { - AddonData* data = raw_data; - if (data->print) { - printf("deleting addon data\n"); - } - if (data->js_cb_ref != NULL) { - NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); - } - free(data); -} - -static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) { - AddonData* data; - - NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); - data->print = true; - - return NULL; -} - -static void TestFinalizer(napi_env env, void* raw_data, void* hint) { - (void) raw_data; - (void) hint; - - AddonData* data; - NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); - napi_value js_cb, undefined; - NODE_API_CALL_RETURN_VOID(env, - napi_get_reference_value(env, data->js_cb_ref, &js_cb)); - NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); - NODE_API_CALL_RETURN_VOID(env, - napi_call_function(env, undefined, js_cb, 0, NULL, NULL)); - NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); - data->js_cb_ref = NULL; -} - -static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) { - AddonData* data; - napi_value result, js_cb; - size_t argc = 1; - - NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); - NODE_API_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); - NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL)); - NODE_API_CALL(env, napi_create_object(env, &result)); - NODE_API_CALL(env, - napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL)); - NODE_API_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref)); - - return result; -} - -EXTERN_C_START -napi_value Init(napi_env env, napi_value exports) { - AddonData* data = malloc(sizeof(*data)); - data->value = 41; - data->print = false; - data->js_cb_ref = NULL; - - NODE_API_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); - - napi_property_descriptor props[] = { - DECLARE_NODE_API_PROPERTY("increment", Increment), - DECLARE_NODE_API_PROPERTY("setPrintOnDelete", SetPrintOnDelete), - DECLARE_NODE_API_PROPERTY("objectWithFinalizer", ObjectWithFinalizer), - }; - - NODE_API_CALL(env, - napi_define_properties( - env, exports, sizeof(props) / sizeof(*props), props)); - - return exports; -} -EXTERN_C_END From 36dfc98a0b2d48460e03fda6c1c3df728eca25ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 8 Mar 2026 07:01:52 +0100 Subject: [PATCH 8/8] Revert mustCall to process-exit pattern matching Node.js common.mustCall Use a regular function wrapper with fn.apply(this, args) to preserve receiver binding, and verify call counts on process exit instead of returning a [wrapper, called] Promise tuple. Co-Authored-By: Claude Opus 4.6 --- implementors/node/must-call.js | 46 ++++++++++++++---------- tests/harness/must-call.js | 17 +++------ tests/js-native-api/3_callbacks/test.js | 14 +++----- tests/js-native-api/test_promise/test.js | 36 +++++++++---------- 4 files changed, 53 insertions(+), 60 deletions(-) diff --git a/implementors/node/must-call.js b/implementors/node/must-call.js index 592357b..a397bd4 100644 --- a/implementors/node/must-call.js +++ b/implementors/node/must-call.js @@ -1,27 +1,26 @@ +const pendingCalls = []; + /** - * Wraps a function and returns a [wrapper, called] tuple. - * - `wrapper` — call this in place of the original function - * - `called` — a Promise that resolves (with the return value of fn) once - * wrapper has been invoked - * - * If `fn` is omitted, a no-op function is used. + * Wraps a function and asserts it is called exactly `exact` times before the + * process exits. If `fn` is omitted, a no-op function is used. * * Usage: - * const [onResolve, resolved] = mustCall((result) => { + * promise.then(mustCall((result) => { * assert.strictEqual(result, 42); - * }); - * promise.then(onResolve); - * await resolved; + * })); */ -const mustCall = (fn) => { - let resolve; - const called = new Promise((r) => { resolve = r; }); - const wrapper = (...args) => { - const result = fn ? fn(...args) : undefined; - resolve(result); - return result; +const mustCall = (fn, exact = 1) => { + const entry = { + exact, + actual: 0, + name: fn?.name || "", + error: new Error(), // capture call-site stack + }; + pendingCalls.push(entry); + return function(...args) { + entry.actual++; + if (fn) return fn.apply(this, args); }; - return [wrapper, called]; }; /** @@ -33,4 +32,15 @@ const mustNotCall = (msg) => { }; }; +process.on("exit", () => { + for (const entry of pendingCalls) { + if (entry.actual !== entry.exact) { + entry.error.message = + `mustCall "${entry.name}" expected ${entry.exact} call(s) ` + + `but got ${entry.actual}`; + throw entry.error; + } + } +}); + Object.assign(globalThis, { mustCall, mustNotCall }); diff --git a/tests/harness/must-call.js b/tests/harness/must-call.js index 5e2cb43..1445bed 100644 --- a/tests/harness/must-call.js +++ b/tests/harness/must-call.js @@ -3,34 +3,27 @@ if (typeof mustCall !== 'function') { throw new Error('Expected a global mustCall function'); } -// mustCall returns a [wrapper, called] tuple +// mustCall returns a wrapper function (not a tuple) { - const [wrapper, called] = mustCall(); + const wrapper = mustCall(); if (typeof wrapper !== 'function') { - throw new Error('mustCall()[0] must be a function'); - } - if (!(called instanceof Promise)) { - throw new Error('mustCall()[1] must be a Promise'); + throw new Error('mustCall() must return a function'); } wrapper(); - await called; } // mustCall forwards arguments and return value { - const [wrapper, called] = mustCall((a, b) => a + b); + const wrapper = mustCall((a, b) => a + b); const result = wrapper(2, 3); assert.strictEqual(result, 5); - const resolvedValue = await called; - assert.strictEqual(resolvedValue, 5); } // mustCall without fn argument works as a no-op wrapper { - const [wrapper, called] = mustCall(); + const wrapper = mustCall(); const result = wrapper('ignored'); assert.strictEqual(result, undefined); - await called; } // mustNotCall is a function diff --git a/tests/js-native-api/3_callbacks/test.js b/tests/js-native-api/3_callbacks/test.js index 8dfc464..ee1b4c6 100644 --- a/tests/js-native-api/3_callbacks/test.js +++ b/tests/js-native-api/3_callbacks/test.js @@ -1,20 +1,14 @@ 'use strict'; const addon = loadAddon('3_callbacks'); -let called = false; -addon.RunCallback((msg) => { +addon.RunCallback(mustCall((msg) => { assert.strictEqual(msg, 'hello world'); - called = true; -}); -assert(called); +})); function testRecv(desiredRecv) { - let recvCalled = false; - addon.RunCallbackWithRecv(function() { + addon.RunCallbackWithRecv(mustCall(function() { assert.strictEqual(this, desiredRecv); - recvCalled = true; - }, desiredRecv); - assert(recvCalled); + }), desiredRecv); } testRecv(undefined); diff --git a/tests/js-native-api/test_promise/test.js b/tests/js-native-api/test_promise/test.js index 627cac8..0050252 100644 --- a/tests/js-native-api/test_promise/test.js +++ b/tests/js-native-api/test_promise/test.js @@ -5,37 +5,35 @@ const test_promise = loadAddon('test_promise'); { const expected_result = 42; const promise = test_promise.createPromise(); - const [onResolve, resolved] = mustCall((result) => { - assert.strictEqual(result, expected_result); - }); - promise.then(onResolve); + promise.then( + mustCall((result) => { + assert.strictEqual(result, expected_result); + })); test_promise.concludeCurrentPromise(expected_result, true); - await resolved; } // A rejection { const expected_result = 'It\'s not you, it\'s me.'; const promise = test_promise.createPromise(); - const [onReject, rejected] = mustCall((result) => { - assert.strictEqual(result, expected_result); - }); - const [onThen, thenCalled] = mustCall(); - promise.then(mustNotCall(), onReject).then(onThen); + promise.then( + mustNotCall(), + mustCall(function(result) { + assert.strictEqual(result, expected_result); + })) + .then(mustCall()); test_promise.concludeCurrentPromise(expected_result, false); - await thenCalled; } // Chaining { const expected_result = 'chained answer'; const promise = test_promise.createPromise(); - const [onResolve, resolved] = mustCall((result) => { - assert.strictEqual(result, expected_result); - }); - promise.then(onResolve); + promise.then( + mustCall((result) => { + assert.strictEqual(result, expected_result); + })); test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); - await resolved; } const promiseTypeTestPromise = test_promise.createPromise(); @@ -45,11 +43,9 @@ test_promise.concludeCurrentPromise(undefined, true); const rejectPromise = Promise.reject(-1); const expected_reason = -1; assert.strictEqual(test_promise.isPromise(rejectPromise), true); -const [onCatch, caught] = mustCall((reason) => { +rejectPromise.catch(mustCall((reason) => { assert.strictEqual(reason, expected_reason); -}); -rejectPromise.catch(onCatch); -await caught; +})); assert.strictEqual(test_promise.isPromise(2.4), false); assert.strictEqual(test_promise.isPromise('I promise!'), false);