From ea315e83d8267bc267ad41a4c934efba8a4fe6b2 Mon Sep 17 00:00:00 2001 From: Balakrishna Avulapati Date: Mon, 10 Aug 2026 13:11:16 +0530 Subject: [PATCH] feat: port test_general core to CTS --- PORTING.md | 2 +- .../js-native-api/test_general/CMakeLists.txt | 1 + tests/js-native-api/test_general/test.js | 96 ++++++++ .../js-native-api/test_general/testGlobals.js | 4 + .../js-native-api/test_general/testNapiRun.js | 7 + .../test_general/testNapiStatus.js | 10 + .../js-native-api/test_general/test_general.c | 229 ++++++++++++++++++ 7 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 tests/js-native-api/test_general/CMakeLists.txt create mode 100644 tests/js-native-api/test_general/test.js create mode 100644 tests/js-native-api/test_general/testGlobals.js create mode 100644 tests/js-native-api/test_general/testNapiRun.js create mode 100644 tests/js-native-api/test_general/testNapiStatus.js create mode 100644 tests/js-native-api/test_general/test_general.c diff --git a/PORTING.md b/PORTING.md index 4607b87..dd42faf 100644 --- a/PORTING.md +++ b/PORTING.md @@ -58,7 +58,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_exception` | Ported ✅ | Medium | | `test_finalizer` | Ported ✅ | Medium | | `test_function` | Ported ✅ | Medium | -| `test_general` | Not ported | Hard | +| `test_general` | Partial | Hard | | `test_handle_scope` | Ported ✅ | Easy | | `test_instance_data` | Not ported | Medium | | `test_new_target` | Ported ✅ | Easy | diff --git a/tests/js-native-api/test_general/CMakeLists.txt b/tests/js-native-api/test_general/CMakeLists.txt new file mode 100644 index 0000000..1d36e70 --- /dev/null +++ b/tests/js-native-api/test_general/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_addon(test_general test_general.c) diff --git a/tests/js-native-api/test_general/test.js b/tests/js-native-api/test_general/test.js new file mode 100644 index 0000000..54450d1 --- /dev/null +++ b/tests/js-native-api/test_general/test.js @@ -0,0 +1,96 @@ +const test_general = loadAddon('test_general'); + +const val1 = '1'; +const val2 = 1; +const val3 = 1; + +class BaseClass { +} + +class ExtendedClass extends BaseClass { +} + +const baseObject = new BaseClass(); +const extendedObject = new ExtendedClass(); + +// napi_strict_equals +assert.ok(test_general.testStrictEquals(val1, val1)); +assert.strictEqual(test_general.testStrictEquals(val1, val2), false); +assert.ok(test_general.testStrictEquals(val2, val3)); + +// napi_get_prototype +assert.strictEqual( + test_general.testGetPrototype(baseObject), + Object.getPrototypeOf(baseObject), +); +assert.strictEqual( + test_general.testGetPrototype(extendedObject), + Object.getPrototypeOf(extendedObject), +); +// Prototypes for base and extended should be different. +assert.notStrictEqual( + test_general.testGetPrototype(baseObject), + test_general.testGetPrototype(extendedObject), +); + +// napi_get_version. Upstream pins this to Node.js's own Node-API version; +// portably, the addon must report whatever version the runtime declares. +assert.strictEqual(test_general.testGetVersion(), napiVersion); + +// napi_typeof +[ + 123, + 'test string', + function() {}, + new Object(), + true, + undefined, + Symbol(), +].forEach((val) => { + assert.strictEqual(test_general.testNapiTypeof(val), typeof val); +}); + +// typeof null is 'object' in JS, so napi_null gets its own case. +assert.strictEqual(test_general.testNapiTypeof(null), 'null'); + +// Wrapping the same object twice fails. +const x = {}; +test_general.wrap(x); +assert.throws( + () => test_general.wrap(x), + { name: 'Error', message: 'Invalid argument' }, +); +// Clean up here, otherwise derefItemWasCalled() will be polluted. +test_general.removeWrap(x); + +// Wrapping twice succeeds if a removeWrap() separates the instances. +const y = {}; +test_general.wrap(y); +test_general.removeWrap(y); +test_general.wrap(y); +// Clean up here, otherwise derefItemWasCalled() will be polluted. +test_general.removeWrap(y); + +// napi_adjust_external_memory +const adjustedValue = test_general.testAdjustExternalMemory(); +assert.strictEqual(typeof adjustedValue, 'number'); +assert.ok(adjustedValue > 0); + +// Garbage collecting a wrapped object calls the finalizer. +assert.strictEqual(test_general.derefItemWasCalled(), false); + +(() => test_general.wrap({}))(); +await gcUntil( + 'deref_item() was called upon garbage collecting a wrapped object.', + () => test_general.derefItemWasCalled(), +); + +// Removing a wrap and then garbage collecting does not call the finalizer. +let z = {}; +test_general.testFinalizeWrap(z); +test_general.removeWrap(z); +z = null; +await gcUntil( + 'finalize callback was not called upon garbage collection.', + () => !test_general.finalizeWasCalled(), +); diff --git a/tests/js-native-api/test_general/testGlobals.js b/tests/js-native-api/test_general/testGlobals.js new file mode 100644 index 0000000..ad33b54 --- /dev/null +++ b/tests/js-native-api/test_general/testGlobals.js @@ -0,0 +1,4 @@ +const test_general = loadAddon('test_general'); + +assert.strictEqual(test_general.getUndefined(), undefined); +assert.strictEqual(test_general.getNull(), null); diff --git a/tests/js-native-api/test_general/testNapiRun.js b/tests/js-native-api/test_general/testNapiRun.js new file mode 100644 index 0000000..d11ed81 --- /dev/null +++ b/tests/js-native-api/test_general/testNapiRun.js @@ -0,0 +1,7 @@ +const test_general = loadAddon('test_general'); + +assert.strictEqual(test_general.testNapiRun('(41.92 + 0.08);'), 42); +assert.throws( + () => test_general.testNapiRun({ abc: 'def' }), + /string was expected/, +); diff --git a/tests/js-native-api/test_general/testNapiStatus.js b/tests/js-native-api/test_general/testNapiStatus.js new file mode 100644 index 0000000..1a1cdf3 --- /dev/null +++ b/tests/js-native-api/test_general/testNapiStatus.js @@ -0,0 +1,10 @@ +const test_general = loadAddon('test_general'); + +// createNapiError provokes a failing call, then checks that +// napi_get_last_error_info reports that failure. The next successful call must +// reset the recorded status back to napi_ok. +test_general.createNapiError(); +assert.ok( + test_general.testNapiErrorCleanup(), + 'napi_status cleaned up for second call', +); diff --git a/tests/js-native-api/test_general/test_general.c b/tests/js-native-api/test_general/test_general.c new file mode 100644 index 0000000..03826ea --- /dev/null +++ b/tests/js-native-api/test_general/test_general.c @@ -0,0 +1,229 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value testStrictEquals(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 bool_result; + napi_value result; + NODE_API_CALL(env, napi_strict_equals(env, args[0], args[1], &bool_result)); + NODE_API_CALL(env, napi_get_boolean(env, bool_result, &result)); + + return result; +} + +static napi_value testGetPrototype(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 result; + NODE_API_CALL(env, napi_get_prototype(env, args[0], &result)); + + return result; +} + +static napi_value testGetVersion(napi_env env, napi_callback_info info) { + uint32_t version; + napi_value result; + NODE_API_CALL(env, napi_get_version(env, &version)); + NODE_API_CALL(env, napi_create_uint32(env, version, &result)); + return result; +} + +static napi_value getNull(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_get_null(env, &result)); + return result; +} + +static napi_value getUndefined(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_get_undefined(env, &result)); + return result; +} + +static napi_value createNapiError(napi_env env, napi_callback_info info) { + napi_value value; + NODE_API_CALL(env, napi_create_string_utf8(env, "xyz", 3, &value)); + + double double_value; + napi_status status = napi_get_value_double(env, value, &double_value); + + NODE_API_ASSERT(env, status != napi_ok, "Failed to produce error condition"); + + const napi_extended_error_info* error_info = 0; + NODE_API_CALL(env, napi_get_last_error_info(env, &error_info)); + + NODE_API_ASSERT(env, + error_info->error_code == status, + "Last error info code should match last status"); + NODE_API_ASSERT(env, + error_info->error_message, + "Last error info message should not be null"); + + return NULL; +} + +static napi_value testNapiErrorCleanup(napi_env env, napi_callback_info info) { + const napi_extended_error_info* error_info = 0; + NODE_API_CALL(env, napi_get_last_error_info(env, &error_info)); + + napi_value result; + bool is_ok = error_info->error_code == napi_ok; + NODE_API_CALL(env, napi_get_boolean(env, is_ok, &result)); + + return result; +} + +static napi_value testNapiTypeof(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_valuetype argument_type; + NODE_API_CALL(env, napi_typeof(env, args[0], &argument_type)); + + const char* name = NULL; + switch (argument_type) { + case napi_number: name = "number"; break; + case napi_string: name = "string"; break; + case napi_function: name = "function"; break; + case napi_object: name = "object"; break; + case napi_boolean: name = "boolean"; break; + case napi_undefined: name = "undefined"; break; + case napi_symbol: name = "symbol"; break; + case napi_null: name = "null"; break; + default: return NULL; + } + + napi_value result; + NODE_API_CALL( + env, napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &result)); + return result; +} + +static bool deref_item_called = false; + +static void deref_item(node_api_basic_env env, void* data, void* hint) { + (void)hint; + + NODE_API_BASIC_ASSERT_RETURN_VOID( + data == &deref_item_called, + "Finalize callback was called with the correct pointer"); + + deref_item_called = true; +} + +static napi_value deref_item_was_called(napi_env env, napi_callback_info info) { + napi_value it_was_called; + + NODE_API_CALL(env, napi_get_boolean(env, deref_item_called, &it_was_called)); + + return it_was_called; +} + +static napi_value wrap_first_arg(napi_env env, + napi_callback_info info, + node_api_basic_finalize finalizer, + void* data) { + size_t argc = 1; + napi_value to_wrap; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &to_wrap, NULL, NULL)); + NODE_API_CALL(env, napi_wrap(env, to_wrap, data, finalizer, NULL, NULL)); + + return to_wrap; +} + +static napi_value wrap(napi_env env, napi_callback_info info) { + deref_item_called = false; + return wrap_first_arg(env, info, deref_item, &deref_item_called); +} + +static napi_value remove_wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value wrapped; + void* data; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL)); + NODE_API_CALL(env, napi_remove_wrap(env, wrapped, &data)); + + return NULL; +} + +static bool finalize_called = false; + +static void test_finalize(node_api_basic_env env, void* data, void* hint) { + (void)env; + (void)data; + (void)hint; + + finalize_called = true; +} + +static napi_value test_finalize_wrap(napi_env env, napi_callback_info info) { + return wrap_first_arg(env, info, test_finalize, NULL); +} + +static napi_value finalize_was_called(napi_env env, napi_callback_info info) { + napi_value it_was_called; + + NODE_API_CALL(env, napi_get_boolean(env, finalize_called, &it_was_called)); + + return it_was_called; +} + +static napi_value testAdjustExternalMemory(napi_env env, + napi_callback_info info) { + napi_value result; + int64_t adjustedValue; + + NODE_API_CALL(env, napi_adjust_external_memory(env, 1, &adjustedValue)); + NODE_API_CALL(env, napi_create_double(env, (double)adjustedValue, &result)); + + return result; +} + +static napi_value testNapiRun(napi_env env, napi_callback_info info) { + napi_value script, result; + size_t argc = 1; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &script, NULL, NULL)); + NODE_API_CALL(env, napi_run_script(env, script, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("testStrictEquals", testStrictEquals), + DECLARE_NODE_API_PROPERTY("testGetPrototype", testGetPrototype), + DECLARE_NODE_API_PROPERTY("testGetVersion", testGetVersion), + DECLARE_NODE_API_PROPERTY("testNapiRun", testNapiRun), + DECLARE_NODE_API_PROPERTY("getUndefined", getUndefined), + DECLARE_NODE_API_PROPERTY("getNull", getNull), + DECLARE_NODE_API_PROPERTY("createNapiError", createNapiError), + DECLARE_NODE_API_PROPERTY("testNapiErrorCleanup", testNapiErrorCleanup), + DECLARE_NODE_API_PROPERTY("testNapiTypeof", testNapiTypeof), + DECLARE_NODE_API_PROPERTY("wrap", wrap), + DECLARE_NODE_API_PROPERTY("removeWrap", remove_wrap), + DECLARE_NODE_API_PROPERTY("testFinalizeWrap", test_finalize_wrap), + DECLARE_NODE_API_PROPERTY("finalizeWasCalled", finalize_was_called), + DECLARE_NODE_API_PROPERTY("derefItemWasCalled", deref_item_was_called), + DECLARE_NODE_API_PROPERTY("testAdjustExternalMemory", + testAdjustExternalMemory)}; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END