diff --git a/PORTING.md b/PORTING.md index 834d704..e303d1f 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` | Not ported | Medium | +| `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` | Not ported | Medium | +| `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 diff --git a/implementors/node/must-call.js b/implementors/node/must-call.js new file mode 100644 index 0000000..a397bd4 --- /dev/null +++ b/implementors/node/must-call.js @@ -0,0 +1,46 @@ +const pendingCalls = []; + +/** + * 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: + * promise.then(mustCall((result) => { + * assert.strictEqual(result, 42); + * })); + */ +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); + }; +}; + +/** + * Returns a function that throws immediately if called. + */ +const mustNotCall = (msg) => { + return () => { + throw new Error(msg || "mustNotCall function was called"); + }; +}; + +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/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/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/harness/must-call.js b/tests/harness/must-call.js new file mode 100644 index 0000000..1445bed --- /dev/null +++ b/tests/harness/must-call.js @@ -0,0 +1,70 @@ +// mustCall is a function +if (typeof mustCall !== 'function') { + throw new Error('Expected a global mustCall function'); +} + +// mustCall returns a wrapper function (not a tuple) +{ + const wrapper = mustCall(); + if (typeof wrapper !== 'function') { + throw new Error('mustCall() must return a function'); + } + wrapper(); +} + +// mustCall forwards arguments and return value +{ + const wrapper = mustCall((a, b) => a + b); + const result = wrapper(2, 3); + assert.strictEqual(result, 5); +} + +// mustCall without fn argument works as a no-op wrapper +{ + const wrapper = mustCall(); + const result = wrapper('ignored'); + assert.strictEqual(result, undefined); +} + +// 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'); +} 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..ee1b4c6 --- /dev/null +++ b/tests/js-native-api/3_callbacks/test.js @@ -0,0 +1,20 @@ +'use strict'; +const addon = loadAddon('3_callbacks'); + +addon.RunCallback(mustCall((msg) => { + assert.strictEqual(msg, 'hello world'); +})); + +function testRecv(desiredRecv) { + addon.RunCallbackWithRecv(mustCall(function() { + assert.strictEqual(this, desiredRecv); + }), desiredRecv); +} + +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_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_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..0050252 --- /dev/null +++ b/tests/js-native-api/test_promise/test.js @@ -0,0 +1,54 @@ +'use strict'; +const test_promise = loadAddon('test_promise'); + +// A resolution +{ + const expected_result = 42; + const promise = test_promise.createPromise(); + promise.then( + mustCall((result) => { + assert.strictEqual(result, expected_result); + })); + test_promise.concludeCurrentPromise(expected_result, true); +} + +// A rejection +{ + const expected_result = 'It\'s not you, it\'s me.'; + const promise = test_promise.createPromise(); + promise.then( + mustNotCall(), + mustCall(function(result) { + assert.strictEqual(result, expected_result); + })) + .then(mustCall()); + test_promise.concludeCurrentPromise(expected_result, false); +} + +// Chaining +{ + const expected_result = 'chained answer'; + const promise = test_promise.createPromise(); + promise.then( + mustCall((result) => { + assert.strictEqual(result, expected_result); + })); + test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); +} + +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); +rejectPromise.catch(mustCall((reason) => { + assert.strictEqual(reason, expected_reason); +})); + +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