diff --git a/CMakeLists.txt b/CMakeLists.txt index 32578ac..cf67540 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,7 @@ function(add_node_api_cts_addon ADDON_NAME) endfunction() function(add_node_api_cts_experimental_addon ADDON_NAME) - cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "SOURCES") - add_node_api_cts_addon(${ADDON_NAME} ${ARG_SOURCES}) + add_node_api_cts_addon(${ADDON_NAME} ${ARGN}) target_compile_definitions(${ADDON_NAME} PRIVATE NAPI_EXPERIMENTAL) if(MSVC) target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_EXPERIMENTAL_LIB}) diff --git a/PORTING.md b/PORTING.md index 4faac6b..67dd010 100644 --- a/PORTING.md +++ b/PORTING.md @@ -68,7 +68,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_properties` | Ported ✅ | Easy | | `test_reference` | Not ported | Medium | | `test_reference_double_free` | Ported ✅ | Easy | -| `test_sharedarraybuffer` | Not ported | Medium | +| `test_sharedarraybuffer` | Ported ✅ | Medium | | `test_string` | Ported ✅ | Medium | | `test_symbol` | Ported ✅ | Easy | | `test_typedarray` | Ported ✅ | Medium | diff --git a/implementors/node/features.js b/implementors/node/features.js index 27286f8..f2123f3 100644 --- a/implementors/node/features.js +++ b/implementors/node/features.js @@ -1,8 +1,14 @@ // Declares which experimental Node-API features this runtime supports. // Each key corresponds to a NODE_API_EXPERIMENTAL_HAS_* compile-time macro. // Other implementors should set unsupported features to false or omit them. + +const [major, minor] = process.version.slice(1).split('.').map(Number); + globalThis.experimentalFeatures = { - sharedArrayBuffer: true, + // node_api_is_sharedarraybuffer and node_api_create_sharedarraybuffer were + // added in Node.js v24.9.0. Earlier versions do not export these symbols, + // causing addons that reference them to fail at dlopen time. + sharedArrayBuffer: major >= 25 || (major === 24 && minor >= 9), createObjectWithProperties: true, setPrototype: true, postFinalizer: true, diff --git a/implementors/node/process.js b/implementors/node/process.js new file mode 100644 index 0000000..b1eb4b7 --- /dev/null +++ b/implementors/node/process.js @@ -0,0 +1,7 @@ +const napiVersion = Number(process.versions.napi); + +const skipTest = () => { + process.exit(0); +}; + +Object.assign(globalThis, { napiVersion, skipTest }); diff --git a/implementors/node/tests.ts b/implementors/node/tests.ts index d4f8b2c..0811aac 100644 --- a/implementors/node/tests.ts +++ b/implementors/node/tests.ts @@ -34,6 +34,12 @@ const GC_MODULE_PATH = path.join( "node", "gc.js" ); +const PROCESS_MODULE_PATH = path.join( + ROOT_PATH, + "implementors", + "node", + "process.js" +); const MUST_CALL_MODULE_PATH = path.join( ROOT_PATH, "implementors", @@ -79,6 +85,8 @@ export function runFileInSubprocess( "--import", "file://" + GC_MODULE_PATH, "--import", + "file://" + PROCESS_MODULE_PATH, + "--import", "file://" + MUST_CALL_MODULE_PATH, filePath, ], diff --git a/tests/js-native-api/test_sharedarraybuffer/CMakeLists.txt b/tests/js-native-api/test_sharedarraybuffer/CMakeLists.txt new file mode 100644 index 0000000..d9b8aba --- /dev/null +++ b/tests/js-native-api/test_sharedarraybuffer/CMakeLists.txt @@ -0,0 +1 @@ +add_node_api_cts_experimental_addon(test_sharedarraybuffer test_sharedarraybuffer.c) diff --git a/tests/js-native-api/test_sharedarraybuffer/test.js b/tests/js-native-api/test_sharedarraybuffer/test.js new file mode 100644 index 0000000..e89e4d6 --- /dev/null +++ b/tests/js-native-api/test_sharedarraybuffer/test.js @@ -0,0 +1,91 @@ +"use strict"; + +// SharedArrayBuffer support is an experimental feature. +if (!experimentalFeatures.sharedArrayBuffer) { + skipTest(); +} + +const test_sharedarraybuffer = loadAddon("test_sharedarraybuffer"); + +{ + const sab = new SharedArrayBuffer(16); + const ab = new ArrayBuffer(16); + const obj = {}; + const arr = []; + + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(sab), + true, + ); + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(ab), + false, + ); + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(obj), + false, + ); + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(arr), + false, + ); + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(null), + false, + ); + assert.strictEqual( + test_sharedarraybuffer.TestIsSharedArrayBuffer(undefined), + false, + ); +} + +// Test node_api_create_sharedarraybuffer +{ + const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(16); + assert(sab instanceof SharedArrayBuffer); + assert.strictEqual(sab.byteLength, 16); +} + +// Test node_api_create_get_sharedarraybuffer_info +{ + const sab = new SharedArrayBuffer(32); + const byteLength = test_sharedarraybuffer.TestGetSharedArrayBufferInfo(sab); + assert.strictEqual(byteLength, 32); +} + +// Test data access +{ + const sab = new SharedArrayBuffer(8); + const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab); + assert.strictEqual(result, true); + + // Check if data was written correctly + const view = new Uint8Array(sab); + for (let i = 0; i < 8; i++) { + assert.strictEqual(view[i], i % 256); + } +} + +// Test data pointer from existing SharedArrayBuffer +{ + const sab = new SharedArrayBuffer(16); + const result = test_sharedarraybuffer.TestSharedArrayBufferData(sab); + assert.strictEqual(result, true); +} + +// Test zero-length SharedArrayBuffer +{ + const sab = test_sharedarraybuffer.TestCreateSharedArrayBuffer(0); + assert(sab instanceof SharedArrayBuffer); + assert.strictEqual(sab.byteLength, 0); +} + +// Test invalid arguments +{ + assert.throws( + () => { + test_sharedarraybuffer.TestGetSharedArrayBufferInfo({}); + }, + { name: "Error", message: "Invalid argument" }, + ); +} diff --git a/tests/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c b/tests/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c new file mode 100644 index 0000000..661e52c --- /dev/null +++ b/tests/js-native-api/test_sharedarraybuffer/test_sharedarraybuffer.c @@ -0,0 +1,130 @@ +#define NAPI_EXPERIMENTAL +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestIsSharedArrayBuffer(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"); + + bool is_sharedarraybuffer; + NODE_API_CALL( + env, node_api_is_sharedarraybuffer(env, args[0], &is_sharedarraybuffer)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, is_sharedarraybuffer, &ret)); + + return ret; +} + +static napi_value TestCreateSharedArrayBuffer(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 byte_length; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &byte_length)); + + NODE_API_ASSERT(env, + byte_length >= 0, + "Invalid byte length. Expects a non-negative integer."); + + napi_value ret; + void* data; + NODE_API_CALL( + env, node_api_create_sharedarraybuffer(env, byte_length, &data, &ret)); + + return ret; +} + +static napi_value TestGetSharedArrayBufferInfo(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"); + + void* data; + size_t byte_length; + NODE_API_CALL(env, + napi_get_arraybuffer_info(env, args[0], &data, &byte_length)); + + napi_value ret; + NODE_API_CALL(env, napi_create_uint32(env, byte_length, &ret)); + + return ret; +} + +static void WriteTestDataToBuffer(void* data, size_t byte_length) { + if (byte_length > 0 && data != NULL) { + uint8_t* bytes = (uint8_t*)data; + for (size_t i = 0; i < byte_length; i++) { + bytes[i] = i % 256; + } + } +} + +static napi_value TestSharedArrayBufferData(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"); + + void* data; + size_t byte_length; + NODE_API_CALL(env, + napi_get_arraybuffer_info(env, args[0], &data, &byte_length)); + + WriteTestDataToBuffer(data, byte_length); + + // Return the same data pointer validity + bool data_valid = (data != NULL) && (byte_length > 0); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, data_valid, &ret)); + + return ret; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("TestIsSharedArrayBuffer", + TestIsSharedArrayBuffer), + DECLARE_NODE_API_PROPERTY("TestCreateSharedArrayBuffer", + TestCreateSharedArrayBuffer), + DECLARE_NODE_API_PROPERTY("TestGetSharedArrayBufferInfo", + TestGetSharedArrayBufferInfo), + DECLARE_NODE_API_PROPERTY("TestSharedArrayBufferData", + TestSharedArrayBufferData), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END