From d6d93890af212c12e0c1588bf763eaec8231b0a4 Mon Sep 17 00:00:00 2001 From: Balakrishna Avulapati Date: Wed, 1 Apr 2026 11:07:07 +0530 Subject: [PATCH 1/4] port test_string to CTS Ports [test_string](https://github.com/nodejs/node/tree/main/test/js-native-api/test_string) from the Node.js test suite to the CTS. - Most of the files are identical except for the js files and CMakeLists.txt - js files use global assert and loadAddon instead of requiring modules - CMakeLists.txt sets NAPI_VERSION >= 10, as API like node_api_create_external_string_latin1 are available only from NAPI_VERSION >= 10. Ref [docs](https://nodejs.org/docs/latest/api/n-api.html#node-api-create-external-string-latin1) Signed-off-by: Balakrishna Avulapati --- PORTING.md | 2 +- .../js-native-api/test_string/CMakeLists.txt | 3 + tests/js-native-api/test_string/test.js | 104 ++++ tests/js-native-api/test_string/test_null.c | 71 +++ tests/js-native-api/test_string/test_null.h | 8 + tests/js-native-api/test_string/test_null.js | 15 + tests/js-native-api/test_string/test_string.c | 498 ++++++++++++++++++ 7 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 tests/js-native-api/test_string/CMakeLists.txt create mode 100644 tests/js-native-api/test_string/test.js create mode 100644 tests/js-native-api/test_string/test_null.c create mode 100644 tests/js-native-api/test_string/test_null.h create mode 100644 tests/js-native-api/test_string/test_null.js create mode 100644 tests/js-native-api/test_string/test_string.c diff --git a/PORTING.md b/PORTING.md index cce0205..7a43c77 100644 --- a/PORTING.md +++ b/PORTING.md @@ -69,7 +69,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `test_reference` | Not ported | Medium | | `test_reference_double_free` | Ported ✅ | Easy | | `test_sharedarraybuffer` | Not ported | Medium | -| `test_string` | Not ported | Medium | +| `test_string` | Ported ✅ | Medium | | `test_symbol` | Ported ✅ | Easy | | `test_typedarray` | Not ported | Medium | diff --git a/tests/js-native-api/test_string/CMakeLists.txt b/tests/js-native-api/test_string/CMakeLists.txt new file mode 100644 index 0000000..64116e2 --- /dev/null +++ b/tests/js-native-api/test_string/CMakeLists.txt @@ -0,0 +1,3 @@ +add_node_api_cts_addon(test_string test_string.c test_null.c) +# API like node_api_create_external_string_latin1 are available from NAPI_VERSION >= 10 +target_compile_definitions(test_string PRIVATE NAPI_VERSION=10) diff --git a/tests/js-native-api/test_string/test.js b/tests/js-native-api/test_string/test.js new file mode 100644 index 0000000..13d2381 --- /dev/null +++ b/tests/js-native-api/test_string/test.js @@ -0,0 +1,104 @@ +"use strict"; + +// Testing api calls for string +const test_string = loadAddon("test_string"); +// The insufficient buffer test case allocates a buffer of size 4, including +// the null terminator. +const kInsufficientIdx = 3; + +const asciiCases = [ + "", + "hello world", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "?!@#$%^&*()_+-=[]{}/.,<>'\"\\", +]; + +const latin1Cases = [ + { + str: "¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿", + utf8Length: 62, + utf8InsufficientIdx: 1, + }, + { + str: "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ", + utf8Length: 126, + utf8InsufficientIdx: 1, + }, +]; + +const unicodeCases = [ + { + str: "\u{2003}\u{2101}\u{2001}\u{202}\u{2011}", + utf8Length: 14, + utf8InsufficientIdx: 1, + }, +]; + +function testLatin1Cases(str) { + assert.strictEqual(test_string.TestLatin1(str), str); + assert.strictEqual(test_string.TestLatin1AutoLength(str), str); + assert.strictEqual(test_string.TestLatin1External(str), str); + assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str), str); + assert.strictEqual(test_string.TestPropertyKeyLatin1(str), str); + assert.strictEqual(test_string.TestPropertyKeyLatin1AutoLength(str), str); + assert.strictEqual(test_string.Latin1Length(str), str.length); + + if (str !== "") { + assert.strictEqual( + test_string.TestLatin1Insufficient(str), + str.slice(0, kInsufficientIdx), + ); + } +} + +function testUnicodeCases(str, utf8Length, utf8InsufficientIdx) { + assert.strictEqual(test_string.TestUtf8(str), str); + assert.strictEqual(test_string.TestUtf16(str), str); + assert.strictEqual(test_string.TestUtf8AutoLength(str), str); + assert.strictEqual(test_string.TestUtf16AutoLength(str), str); + assert.strictEqual(test_string.TestUtf16External(str), str); + assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str), str); + assert.strictEqual(test_string.TestPropertyKeyUtf8(str), str); + assert.strictEqual(test_string.TestPropertyKeyUtf8AutoLength(str), str); + assert.strictEqual(test_string.TestPropertyKeyUtf16(str), str); + assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str), str); + assert.strictEqual(test_string.Utf8Length(str), utf8Length); + assert.strictEqual(test_string.Utf16Length(str), str.length); + + if (str !== "") { + assert.strictEqual( + test_string.TestUtf8Insufficient(str), + str.slice(0, utf8InsufficientIdx), + ); + assert.strictEqual( + test_string.TestUtf16Insufficient(str), + str.slice(0, kInsufficientIdx), + ); + } +} + +asciiCases.forEach(testLatin1Cases); +asciiCases.forEach((str) => + testUnicodeCases(str, str.length, kInsufficientIdx), +); +latin1Cases.forEach((it) => testLatin1Cases(it.str)); +latin1Cases.forEach((it) => + testUnicodeCases(it.str, it.utf8Length, it.utf8InsufficientIdx), +); +unicodeCases.forEach((it) => + testUnicodeCases(it.str, it.utf8Length, it.utf8InsufficientIdx), +); + +assert.throws(() => { + test_string.TestLargeUtf8(); +}, /^Error: Invalid argument$/); + +assert.throws(() => { + test_string.TestLargeLatin1(); +}, /^Error: Invalid argument$/); + +assert.throws(() => { + test_string.TestLargeUtf16(); +}, /^Error: Invalid argument$/); + +test_string.TestMemoryCorruption(" ".repeat(64 * 1024)); diff --git a/tests/js-native-api/test_string/test_null.c b/tests/js-native-api/test_string/test_null.c new file mode 100644 index 0000000..72ca286 --- /dev/null +++ b/tests/js-native-api/test_string/test_null.c @@ -0,0 +1,71 @@ +#include + +#include "../common.h" +#include "test_null.h" + +#define DECLARE_TEST(charset, str_arg) \ + static napi_value \ + test_create_##charset(napi_env env, napi_callback_info info) { \ + napi_value return_value, result; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + \ + add_returned_status(env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + napi_create_string_##charset(NULL, \ + (str_arg), \ + NAPI_AUTO_LENGTH, \ + &result)); \ + \ + napi_create_string_##charset(env, NULL, NAPI_AUTO_LENGTH, &result); \ + add_last_status(env, "stringIsNullNonZeroLength", return_value); \ + \ + napi_create_string_##charset(env, NULL, 0, &result); \ + add_last_status(env, "stringIsNullZeroLength", return_value); \ + \ + napi_create_string_##charset(env, (str_arg), NAPI_AUTO_LENGTH, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + \ + return return_value; \ + } + +static const char16_t something[] = { + (char16_t)'s', + (char16_t)'o', + (char16_t)'m', + (char16_t)'e', + (char16_t)'t', + (char16_t)'h', + (char16_t)'i', + (char16_t)'n', + (char16_t)'g', + (char16_t)'\0' +}; + +DECLARE_TEST(utf8, "something") +DECLARE_TEST(latin1, "something") +DECLARE_TEST(utf16, something) + +void init_test_null(napi_env env, napi_value exports) { + napi_value test_null; + + const napi_property_descriptor test_null_props[] = { + DECLARE_NODE_API_PROPERTY("test_create_utf8", test_create_utf8), + DECLARE_NODE_API_PROPERTY("test_create_latin1", test_create_latin1), + DECLARE_NODE_API_PROPERTY("test_create_utf16", test_create_utf16), + }; + + 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)); + + const napi_property_descriptor test_null_set = { + "testNull", NULL, NULL, NULL, NULL, test_null, napi_enumerable, NULL + }; + + NODE_API_CALL_RETURN_VOID(env, + napi_define_properties(env, exports, 1, &test_null_set)); +} diff --git a/tests/js-native-api/test_string/test_null.h b/tests/js-native-api/test_string/test_null.h new file mode 100644 index 0000000..fdeb173 --- /dev/null +++ b/tests/js-native-api/test_string/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_STRING_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_STRING_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_STRING_TEST_NULL_H_ diff --git a/tests/js-native-api/test_string/test_null.js b/tests/js-native-api/test_string/test_null.js new file mode 100644 index 0000000..a01c96d --- /dev/null +++ b/tests/js-native-api/test_string/test_null.js @@ -0,0 +1,15 @@ +"use strict"; + +// Test passing NULL to object-related Node-APIs. +const { testNull } = loadAddon("test_string"); + +const expectedResult = { + envIsNull: "Invalid argument", + stringIsNullNonZeroLength: "Invalid argument", + stringIsNullZeroLength: "napi_ok", + resultIsNull: "Invalid argument", +}; + +assert.deepStrictEqual(expectedResult, testNull.test_create_latin1()); +assert.deepStrictEqual(expectedResult, testNull.test_create_utf8()); +assert.deepStrictEqual(expectedResult, testNull.test_create_utf16()); diff --git a/tests/js-native-api/test_string/test_string.c b/tests/js-native-api/test_string/test_string.c new file mode 100644 index 0000000..01e5dbe --- /dev/null +++ b/tests/js-native-api/test_string/test_string.c @@ -0,0 +1,498 @@ +#include +#include // INT_MAX +#include +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +enum length_type { actual_length, auto_length }; + +static napi_status validate_and_retrieve_single_string_arg( + napi_env env, napi_callback_info info, napi_value* arg) { + size_t argc = 1; + NODE_API_CHECK_STATUS(napi_get_cb_info(env, info, &argc, arg, NULL, NULL)); + + NODE_API_ASSERT_STATUS(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NODE_API_CHECK_STATUS(napi_typeof(env, *arg, &valuetype)); + + NODE_API_ASSERT_STATUS(env, + valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + return napi_ok; +} + +// These help us factor out code that is common between the bindings. +typedef napi_status (*OneByteCreateAPI)(napi_env, + const char*, + size_t, + napi_value*); +typedef napi_status (*OneByteGetAPI)( + napi_env, napi_value, char*, size_t, size_t*); +typedef napi_status (*TwoByteCreateAPI)(napi_env, + const char16_t*, + size_t, + napi_value*); +typedef napi_status (*TwoByteGetAPI)( + napi_env, napi_value, char16_t*, size_t, size_t*); + +// Test passing back the one-byte string we got from JS. +static napi_value TestOneByteImpl(napi_env env, + napi_callback_info info, + OneByteGetAPI get_api, + OneByteCreateAPI create_api, + enum length_type length_mode) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + if (length_mode == auto_length) { + copied = NAPI_AUTO_LENGTH; + } + NODE_API_CALL(env, create_api(env, buffer, copied, &output)); + + return output; +} + +// Test passing back the two-byte string we got from JS. +static napi_value TestTwoByteImpl(napi_env env, + napi_callback_info info, + TwoByteGetAPI get_api, + TwoByteCreateAPI create_api, + enum length_type length_mode) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char16_t buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + if (length_mode == auto_length) { + copied = NAPI_AUTO_LENGTH; + } + NODE_API_CALL(env, create_api(env, buffer, copied, &output)); + + return output; +} + +static void free_string(node_api_basic_env env, void* data, void* hint) { + free(data); +} + +static napi_status create_external_latin1(napi_env env, + const char* string, + size_t length, + napi_value* result) { + napi_status status; + // Initialize to true, because that is the value we don't want. + bool copied = true; + char* string_copy; + const size_t actual_length = + (length == NAPI_AUTO_LENGTH ? strlen(string) : length); + const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); + string_copy = malloc(length_bytes); + memcpy(string_copy, string, length_bytes); + string_copy[actual_length] = 0; + + status = node_api_create_external_string_latin1( + env, string_copy, length, free_string, NULL, result, &copied); + // We do not want the string to be copied. + if (copied) { + return napi_generic_failure; + } + if (status != napi_ok) { + free(string_copy); + return status; + } + return napi_ok; +} + +// strlen for char16_t. Needed in case we're copying a string of length +// NAPI_AUTO_LENGTH. +static size_t strlen16(const char16_t* string) { + for (const char16_t* iter = string;; iter++) { + if (*iter == 0) { + return iter - string; + } + } + // We should never get here. + abort(); +} + +static napi_status create_external_utf16(napi_env env, + const char16_t* string, + size_t length, + napi_value* result) { + napi_status status; + // Initialize to true, because that is the value we don't want. + bool copied = true; + char16_t* string_copy; + const size_t actual_length = + (length == NAPI_AUTO_LENGTH ? strlen16(string) : length); + const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); + string_copy = malloc(length_bytes); + memcpy(string_copy, string, length_bytes); + string_copy[actual_length] = 0; + + status = node_api_create_external_string_utf16( + env, string_copy, length, free_string, NULL, result, &copied); + if (status != napi_ok) { + free(string_copy); + return status; + } + + return napi_ok; +} + +static napi_value TestLatin1(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + napi_create_string_latin1, + actual_length); +} + +static napi_value TestUtf8(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + napi_create_string_utf8, + actual_length); +} + +static napi_value TestUtf16(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + napi_create_string_utf16, + actual_length); +} + +static napi_value TestLatin1AutoLength(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + napi_create_string_latin1, + auto_length); +} + +static napi_value TestUtf8AutoLength(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + napi_create_string_utf8, + auto_length); +} + +static napi_value TestUtf16AutoLength(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + napi_create_string_utf16, + auto_length); +} + +static napi_value TestLatin1External(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + create_external_latin1, + actual_length); +} + +static napi_value TestUtf16External(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + create_external_utf16, + actual_length); +} + +static napi_value TestLatin1ExternalAutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + create_external_latin1, + auto_length); +} + +static napi_value TestUtf16ExternalAutoLength(napi_env env, + napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + create_external_utf16, + auto_length); +} + +static napi_value TestLatin1Insufficient(napi_env env, + napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NODE_API_CALL( + env, + napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NODE_API_CALL(env, napi_create_string_latin1(env, buffer, copied, &output)); + + return output; +} + +static napi_value TestUtf8Insufficient(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NODE_API_CALL( + env, + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NODE_API_CALL(env, napi_create_string_utf8(env, buffer, copied, &output)); + + return output; +} + +static napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char16_t buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NODE_API_CALL( + env, + napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NODE_API_CALL(env, napi_create_string_utf16(env, buffer, copied, &output)); + + return output; +} + +static napi_value TestPropertyKeyLatin1(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + actual_length); +} + +static napi_value TestPropertyKeyLatin1AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + auto_length); +} + +static napi_value TestPropertyKeyUtf8(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + actual_length); +} + +static napi_value TestPropertyKeyUtf8AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + auto_length); +} + +static napi_value TestPropertyKeyUtf16(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + node_api_create_property_key_utf16, + actual_length); +} + +static napi_value TestPropertyKeyUtf16AutoLength(napi_env env, + napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + node_api_create_property_key_utf16, + auto_length); +} + +static napi_value Latin1Length(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + size_t length; + NODE_API_CALL(env, + napi_get_value_string_latin1(env, args[0], NULL, 0, &length)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + +static napi_value Utf16Length(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + size_t length; + NODE_API_CALL(env, + napi_get_value_string_utf16(env, args[0], NULL, 0, &length)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + +static napi_value Utf8Length(napi_env env, napi_callback_info info) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + size_t length; + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[0], NULL, 0, &length)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + +static napi_value TestLargeUtf8(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NODE_API_CALL( + env, napi_create_string_utf8(env, "", ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NODE_API_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + +static napi_value TestLargeLatin1(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NODE_API_CALL( + env, + napi_create_string_latin1(env, "", ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NODE_API_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + +static napi_value TestLargeUtf16(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NODE_API_CALL( + env, + napi_create_string_utf16( + env, ((const char16_t*)""), ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NODE_API_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + +static napi_value TestMemoryCorruption(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"); + + char buf[10] = {0}; + NODE_API_CALL(env, napi_get_value_string_utf8(env, args[0], buf, 0, NULL)); + + char zero[10] = {0}; + if (memcmp(buf, zero, sizeof(buf)) != 0) { + NODE_API_CALL(env, napi_throw_error(env, NULL, "Buffer overwritten")); + } + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("TestLatin1", TestLatin1), + DECLARE_NODE_API_PROPERTY("TestLatin1AutoLength", TestLatin1AutoLength), + DECLARE_NODE_API_PROPERTY("TestLatin1External", TestLatin1External), + DECLARE_NODE_API_PROPERTY("TestLatin1ExternalAutoLength", + TestLatin1ExternalAutoLength), + DECLARE_NODE_API_PROPERTY("TestLatin1Insufficient", + TestLatin1Insufficient), + DECLARE_NODE_API_PROPERTY("TestUtf8", TestUtf8), + DECLARE_NODE_API_PROPERTY("TestUtf8AutoLength", TestUtf8AutoLength), + DECLARE_NODE_API_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient), + DECLARE_NODE_API_PROPERTY("TestUtf16", TestUtf16), + DECLARE_NODE_API_PROPERTY("TestUtf16AutoLength", TestUtf16AutoLength), + DECLARE_NODE_API_PROPERTY("TestUtf16External", TestUtf16External), + DECLARE_NODE_API_PROPERTY("TestUtf16ExternalAutoLength", + TestUtf16ExternalAutoLength), + DECLARE_NODE_API_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient), + DECLARE_NODE_API_PROPERTY("Latin1Length", Latin1Length), + DECLARE_NODE_API_PROPERTY("Utf16Length", Utf16Length), + DECLARE_NODE_API_PROPERTY("Utf8Length", Utf8Length), + DECLARE_NODE_API_PROPERTY("TestLargeUtf8", TestLargeUtf8), + DECLARE_NODE_API_PROPERTY("TestLargeLatin1", TestLargeLatin1), + DECLARE_NODE_API_PROPERTY("TestLargeUtf16", TestLargeUtf16), + DECLARE_NODE_API_PROPERTY("TestMemoryCorruption", TestMemoryCorruption), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1", TestPropertyKeyLatin1), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1AutoLength", + TestPropertyKeyLatin1AutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8", TestPropertyKeyUtf8), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8AutoLength", + TestPropertyKeyUtf8AutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16", TestPropertyKeyUtf16), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16AutoLength", + TestPropertyKeyUtf16AutoLength), + }; + + init_test_null(env, exports); + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END From 5b361aae2d8341ced3570819788fa86260b3536e Mon Sep 17 00:00:00 2001 From: Balakrishna Avulapati Date: Sat, 4 Apr 2026 00:09:54 +0530 Subject: [PATCH 2/4] skip the test if napi version < 10 Signed-off-by: Balakrishna Avulapati --- eslint.config.js | 2 ++ implementors/node/process.js | 7 +++++++ implementors/node/tests.ts | 8 ++++++++ tests/js-native-api/test_string/test.js | 7 +++++++ tests/js-native-api/test_string/test_null.js | 7 +++++++ 5 files changed, 31 insertions(+) create mode 100644 implementors/node/process.js diff --git a/eslint.config.js b/eslint.config.js index fff73c6..e02a875 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,8 @@ export default defineConfig([ mustNotCall: "readonly", gcUntil: "readonly", experimentalFeatures: "readonly", + napiVersion: "readonly", + skipTest: "readonly", }, }, rules: { 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_string/test.js b/tests/js-native-api/test_string/test.js index 13d2381..dc21a3e 100644 --- a/tests/js-native-api/test_string/test.js +++ b/tests/js-native-api/test_string/test.js @@ -1,5 +1,12 @@ "use strict"; +// test_string addon requires Node-API version >= 10 +// (node_api_create_external_string_latin1/utf16). +// Node-API 10 is supported in Node.js >= 22.14.0 and >= 23.6.0. +if (Number(napiVersion) < 10) { + skipTest(); +} + // Testing api calls for string const test_string = loadAddon("test_string"); // The insufficient buffer test case allocates a buffer of size 4, including diff --git a/tests/js-native-api/test_string/test_null.js b/tests/js-native-api/test_string/test_null.js index a01c96d..10121fc 100644 --- a/tests/js-native-api/test_string/test_null.js +++ b/tests/js-native-api/test_string/test_null.js @@ -1,5 +1,12 @@ "use strict"; +// test_string addon requires Node-API version >= 10 +// (node_api_create_external_string_latin1/utf16). +// Node-API 10 is supported in Node.js >= 22.14.0 and >= 23.6.0. +if (Number(napiVersion) < 10) { + skipTest(); +} + // Test passing NULL to object-related Node-APIs. const { testNull } = loadAddon("test_string"); From c6cf45ea5c86481d51491f4fc47eef882b54ff7b Mon Sep 17 00:00:00 2001 From: Balakrishna Avulapati Date: Thu, 9 Apr 2026 17:15:34 +0530 Subject: [PATCH 3/4] split tests to separate v10 sub tests Signed-off-by: Balakrishna Avulapati --- implementors/node/features.js | 6 + implementors/node/process.js | 7 - implementors/node/run-tests.ts | 13 - implementors/node/tests.ts | 8 - .../js-native-api/test_string/CMakeLists.txt | 7 +- tests/js-native-api/test_string/test.js | 17 -- tests/js-native-api/test_string/test_null.js | 7 - tests/js-native-api/test_string/test_string.c | 251 +----------------- .../test_string/test_string_helpers.h | 88 ++++++ .../test_string/test_string_v10.c | 189 +++++++++++++ tests/js-native-api/test_string/test_v10.js | 63 +++++ 11 files changed, 352 insertions(+), 304 deletions(-) delete mode 100644 implementors/node/process.js create mode 100644 tests/js-native-api/test_string/test_string_helpers.h create mode 100644 tests/js-native-api/test_string/test_string_v10.c create mode 100644 tests/js-native-api/test_string/test_v10.js diff --git a/implementors/node/features.js b/implementors/node/features.js index d06bad9..27286f8 100644 --- a/implementors/node/features.js +++ b/implementors/node/features.js @@ -7,3 +7,9 @@ globalThis.experimentalFeatures = { setPrototype: true, postFinalizer: true, }; + +globalThis.napiVersion = Number(process.versions.napi); + +globalThis.skipTest = () => { + process.exit(0); +}; diff --git a/implementors/node/process.js b/implementors/node/process.js deleted file mode 100644 index b1eb4b7..0000000 --- a/implementors/node/process.js +++ /dev/null @@ -1,7 +0,0 @@ -const napiVersion = Number(process.versions.napi); - -const skipTest = () => { - process.exit(0); -}; - -Object.assign(globalThis, { napiVersion, skipTest }); diff --git a/implementors/node/run-tests.ts b/implementors/node/run-tests.ts index ce83184..1d341b7 100644 --- a/implementors/node/run-tests.ts +++ b/implementors/node/run-tests.ts @@ -6,19 +6,6 @@ import { listDirectoryEntries, runFileInSubprocess } from "./tests.ts"; const ROOT_PATH = path.resolve(import.meta.dirname, "..", ".."); const TESTS_ROOT_PATH = path.join(ROOT_PATH, "tests"); -const ASSERT_MODULE_PATH = path.join( - ROOT_PATH, - "implementors", - "node", - "assert.js" -); -const LOAD_ADDON_MODULE_PATH = path.join( - ROOT_PATH, - "implementors", - "node", - "load-addon.js" -); - async function populateSuite( testContext: TestContext, dir: string diff --git a/implementors/node/tests.ts b/implementors/node/tests.ts index 0811aac..d4f8b2c 100644 --- a/implementors/node/tests.ts +++ b/implementors/node/tests.ts @@ -34,12 +34,6 @@ 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", @@ -85,8 +79,6 @@ 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_string/CMakeLists.txt b/tests/js-native-api/test_string/CMakeLists.txt index 64116e2..4a3b402 100644 --- a/tests/js-native-api/test_string/CMakeLists.txt +++ b/tests/js-native-api/test_string/CMakeLists.txt @@ -1,3 +1,6 @@ add_node_api_cts_addon(test_string test_string.c test_null.c) -# API like node_api_create_external_string_latin1 are available from NAPI_VERSION >= 10 -target_compile_definitions(test_string PRIVATE NAPI_VERSION=10) + +# APIs like node_api_create_external_string_latin1 and +# node_api_create_property_key_utf8 are available from NAPI_VERSION >= 10 +add_node_api_cts_addon(test_string_v10 test_string_v10.c) +target_compile_definitions(test_string_v10 PRIVATE NAPI_VERSION=10) diff --git a/tests/js-native-api/test_string/test.js b/tests/js-native-api/test_string/test.js index dc21a3e..21f8567 100644 --- a/tests/js-native-api/test_string/test.js +++ b/tests/js-native-api/test_string/test.js @@ -1,12 +1,5 @@ "use strict"; -// test_string addon requires Node-API version >= 10 -// (node_api_create_external_string_latin1/utf16). -// Node-API 10 is supported in Node.js >= 22.14.0 and >= 23.6.0. -if (Number(napiVersion) < 10) { - skipTest(); -} - // Testing api calls for string const test_string = loadAddon("test_string"); // The insufficient buffer test case allocates a buffer of size 4, including @@ -44,10 +37,6 @@ const unicodeCases = [ function testLatin1Cases(str) { assert.strictEqual(test_string.TestLatin1(str), str); assert.strictEqual(test_string.TestLatin1AutoLength(str), str); - assert.strictEqual(test_string.TestLatin1External(str), str); - assert.strictEqual(test_string.TestLatin1ExternalAutoLength(str), str); - assert.strictEqual(test_string.TestPropertyKeyLatin1(str), str); - assert.strictEqual(test_string.TestPropertyKeyLatin1AutoLength(str), str); assert.strictEqual(test_string.Latin1Length(str), str.length); if (str !== "") { @@ -63,12 +52,6 @@ function testUnicodeCases(str, utf8Length, utf8InsufficientIdx) { assert.strictEqual(test_string.TestUtf16(str), str); assert.strictEqual(test_string.TestUtf8AutoLength(str), str); assert.strictEqual(test_string.TestUtf16AutoLength(str), str); - assert.strictEqual(test_string.TestUtf16External(str), str); - assert.strictEqual(test_string.TestUtf16ExternalAutoLength(str), str); - assert.strictEqual(test_string.TestPropertyKeyUtf8(str), str); - assert.strictEqual(test_string.TestPropertyKeyUtf8AutoLength(str), str); - assert.strictEqual(test_string.TestPropertyKeyUtf16(str), str); - assert.strictEqual(test_string.TestPropertyKeyUtf16AutoLength(str), str); assert.strictEqual(test_string.Utf8Length(str), utf8Length); assert.strictEqual(test_string.Utf16Length(str), str.length); diff --git a/tests/js-native-api/test_string/test_null.js b/tests/js-native-api/test_string/test_null.js index 10121fc..a01c96d 100644 --- a/tests/js-native-api/test_string/test_null.js +++ b/tests/js-native-api/test_string/test_null.js @@ -1,12 +1,5 @@ "use strict"; -// test_string addon requires Node-API version >= 10 -// (node_api_create_external_string_latin1/utf16). -// Node-API 10 is supported in Node.js >= 22.14.0 and >= 23.6.0. -if (Number(napiVersion) < 10) { - skipTest(); -} - // Test passing NULL to object-related Node-APIs. const { testNull } = loadAddon("test_string"); diff --git a/tests/js-native-api/test_string/test_string.c b/tests/js-native-api/test_string/test_string.c index 01e5dbe..979fd43 100644 --- a/tests/js-native-api/test_string/test_string.c +++ b/tests/js-native-api/test_string/test_string.c @@ -5,156 +5,7 @@ #include "../common.h" #include "../entry_point.h" #include "test_null.h" - -enum length_type { actual_length, auto_length }; - -static napi_status validate_and_retrieve_single_string_arg( - napi_env env, napi_callback_info info, napi_value* arg) { - size_t argc = 1; - NODE_API_CHECK_STATUS(napi_get_cb_info(env, info, &argc, arg, NULL, NULL)); - - NODE_API_ASSERT_STATUS(env, argc >= 1, "Wrong number of arguments"); - - napi_valuetype valuetype; - NODE_API_CHECK_STATUS(napi_typeof(env, *arg, &valuetype)); - - NODE_API_ASSERT_STATUS(env, - valuetype == napi_string, - "Wrong type of argment. Expects a string."); - - return napi_ok; -} - -// These help us factor out code that is common between the bindings. -typedef napi_status (*OneByteCreateAPI)(napi_env, - const char*, - size_t, - napi_value*); -typedef napi_status (*OneByteGetAPI)( - napi_env, napi_value, char*, size_t, size_t*); -typedef napi_status (*TwoByteCreateAPI)(napi_env, - const char16_t*, - size_t, - napi_value*); -typedef napi_status (*TwoByteGetAPI)( - napi_env, napi_value, char16_t*, size_t, size_t*); - -// Test passing back the one-byte string we got from JS. -static napi_value TestOneByteImpl(napi_env env, - napi_callback_info info, - OneByteGetAPI get_api, - OneByteCreateAPI create_api, - enum length_type length_mode) { - napi_value args[1]; - NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); - - char buffer[128]; - size_t buffer_size = 128; - size_t copied; - - NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); - - napi_value output; - if (length_mode == auto_length) { - copied = NAPI_AUTO_LENGTH; - } - NODE_API_CALL(env, create_api(env, buffer, copied, &output)); - - return output; -} - -// Test passing back the two-byte string we got from JS. -static napi_value TestTwoByteImpl(napi_env env, - napi_callback_info info, - TwoByteGetAPI get_api, - TwoByteCreateAPI create_api, - enum length_type length_mode) { - napi_value args[1]; - NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); - - char16_t buffer[128]; - size_t buffer_size = 128; - size_t copied; - - NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); - - napi_value output; - if (length_mode == auto_length) { - copied = NAPI_AUTO_LENGTH; - } - NODE_API_CALL(env, create_api(env, buffer, copied, &output)); - - return output; -} - -static void free_string(node_api_basic_env env, void* data, void* hint) { - free(data); -} - -static napi_status create_external_latin1(napi_env env, - const char* string, - size_t length, - napi_value* result) { - napi_status status; - // Initialize to true, because that is the value we don't want. - bool copied = true; - char* string_copy; - const size_t actual_length = - (length == NAPI_AUTO_LENGTH ? strlen(string) : length); - const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); - string_copy = malloc(length_bytes); - memcpy(string_copy, string, length_bytes); - string_copy[actual_length] = 0; - - status = node_api_create_external_string_latin1( - env, string_copy, length, free_string, NULL, result, &copied); - // We do not want the string to be copied. - if (copied) { - return napi_generic_failure; - } - if (status != napi_ok) { - free(string_copy); - return status; - } - return napi_ok; -} - -// strlen for char16_t. Needed in case we're copying a string of length -// NAPI_AUTO_LENGTH. -static size_t strlen16(const char16_t* string) { - for (const char16_t* iter = string;; iter++) { - if (*iter == 0) { - return iter - string; - } - } - // We should never get here. - abort(); -} - -static napi_status create_external_utf16(napi_env env, - const char16_t* string, - size_t length, - napi_value* result) { - napi_status status; - // Initialize to true, because that is the value we don't want. - bool copied = true; - char16_t* string_copy; - const size_t actual_length = - (length == NAPI_AUTO_LENGTH ? strlen16(string) : length); - const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); - string_copy = malloc(length_bytes); - memcpy(string_copy, string, length_bytes); - string_copy[actual_length] = 0; - - status = node_api_create_external_string_utf16( - env, string_copy, length, free_string, NULL, result, &copied); - if (status != napi_ok) { - free(string_copy); - return status; - } - - return napi_ok; -} +#include "test_string_helpers.h" static napi_value TestLatin1(napi_env env, napi_callback_info info) { return TestOneByteImpl(env, @@ -204,40 +55,6 @@ static napi_value TestUtf16AutoLength(napi_env env, napi_callback_info info) { auto_length); } -static napi_value TestLatin1External(napi_env env, napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_latin1, - create_external_latin1, - actual_length); -} - -static napi_value TestUtf16External(napi_env env, napi_callback_info info) { - return TestTwoByteImpl(env, - info, - napi_get_value_string_utf16, - create_external_utf16, - actual_length); -} - -static napi_value TestLatin1ExternalAutoLength(napi_env env, - napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_latin1, - create_external_latin1, - auto_length); -} - -static napi_value TestUtf16ExternalAutoLength(napi_env env, - napi_callback_info info) { - return TestTwoByteImpl(env, - info, - napi_get_value_string_utf16, - create_external_utf16, - auto_length); -} - static napi_value TestLatin1Insufficient(napi_env env, napi_callback_info info) { napi_value args[1]; @@ -293,57 +110,6 @@ static napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) { return output; } -static napi_value TestPropertyKeyLatin1(napi_env env, napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_latin1, - node_api_create_property_key_latin1, - actual_length); -} - -static napi_value TestPropertyKeyLatin1AutoLength(napi_env env, - napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_latin1, - node_api_create_property_key_latin1, - auto_length); -} - -static napi_value TestPropertyKeyUtf8(napi_env env, napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_utf8, - node_api_create_property_key_utf8, - actual_length); -} - -static napi_value TestPropertyKeyUtf8AutoLength(napi_env env, - napi_callback_info info) { - return TestOneByteImpl(env, - info, - napi_get_value_string_utf8, - node_api_create_property_key_utf8, - auto_length); -} - -static napi_value TestPropertyKeyUtf16(napi_env env, napi_callback_info info) { - return TestTwoByteImpl(env, - info, - napi_get_value_string_utf16, - node_api_create_property_key_utf16, - actual_length); -} - -static napi_value TestPropertyKeyUtf16AutoLength(napi_env env, - napi_callback_info info) { - return TestTwoByteImpl(env, - info, - napi_get_value_string_utf16, - node_api_create_property_key_utf16, - auto_length); -} - static napi_value Latin1Length(napi_env env, napi_callback_info info) { napi_value args[1]; NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); @@ -454,9 +220,6 @@ napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor properties[] = { DECLARE_NODE_API_PROPERTY("TestLatin1", TestLatin1), DECLARE_NODE_API_PROPERTY("TestLatin1AutoLength", TestLatin1AutoLength), - DECLARE_NODE_API_PROPERTY("TestLatin1External", TestLatin1External), - DECLARE_NODE_API_PROPERTY("TestLatin1ExternalAutoLength", - TestLatin1ExternalAutoLength), DECLARE_NODE_API_PROPERTY("TestLatin1Insufficient", TestLatin1Insufficient), DECLARE_NODE_API_PROPERTY("TestUtf8", TestUtf8), @@ -464,9 +227,6 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient), DECLARE_NODE_API_PROPERTY("TestUtf16", TestUtf16), DECLARE_NODE_API_PROPERTY("TestUtf16AutoLength", TestUtf16AutoLength), - DECLARE_NODE_API_PROPERTY("TestUtf16External", TestUtf16External), - DECLARE_NODE_API_PROPERTY("TestUtf16ExternalAutoLength", - TestUtf16ExternalAutoLength), DECLARE_NODE_API_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient), DECLARE_NODE_API_PROPERTY("Latin1Length", Latin1Length), DECLARE_NODE_API_PROPERTY("Utf16Length", Utf16Length), @@ -475,15 +235,6 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NODE_API_PROPERTY("TestLargeLatin1", TestLargeLatin1), DECLARE_NODE_API_PROPERTY("TestLargeUtf16", TestLargeUtf16), DECLARE_NODE_API_PROPERTY("TestMemoryCorruption", TestMemoryCorruption), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1", TestPropertyKeyLatin1), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1AutoLength", - TestPropertyKeyLatin1AutoLength), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8", TestPropertyKeyUtf8), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8AutoLength", - TestPropertyKeyUtf8AutoLength), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16", TestPropertyKeyUtf16), - DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16AutoLength", - TestPropertyKeyUtf16AutoLength), }; init_test_null(env, exports); diff --git a/tests/js-native-api/test_string/test_string_helpers.h b/tests/js-native-api/test_string/test_string_helpers.h new file mode 100644 index 0000000..2979a71 --- /dev/null +++ b/tests/js-native-api/test_string/test_string_helpers.h @@ -0,0 +1,88 @@ +#ifndef TEST_JS_NATIVE_API_TEST_STRING_TEST_STRING_HELPERS_H_ +#define TEST_JS_NATIVE_API_TEST_STRING_TEST_STRING_HELPERS_H_ + +#include +#include "../common.h" + +enum length_type { actual_length, auto_length }; + +// These help us factor out code that is common between the bindings. +typedef napi_status (*OneByteCreateAPI)(napi_env, + const char*, + size_t, + napi_value*); +typedef napi_status (*OneByteGetAPI)( + napi_env, napi_value, char*, size_t, size_t*); +typedef napi_status (*TwoByteCreateAPI)(napi_env, + const char16_t*, + size_t, + napi_value*); +typedef napi_status (*TwoByteGetAPI)( + napi_env, napi_value, char16_t*, size_t, size_t*); + +static napi_status validate_and_retrieve_single_string_arg( + napi_env env, napi_callback_info info, napi_value* arg) { + size_t argc = 1; + NODE_API_CHECK_STATUS(napi_get_cb_info(env, info, &argc, arg, NULL, NULL)); + + NODE_API_ASSERT_STATUS(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NODE_API_CHECK_STATUS(napi_typeof(env, *arg, &valuetype)); + + NODE_API_ASSERT_STATUS(env, + valuetype == napi_string, + "Wrong type of argument. Expects a string."); + + return napi_ok; +} + +// Test passing back the one-byte string we got from JS. +static napi_value TestOneByteImpl(napi_env env, + napi_callback_info info, + OneByteGetAPI get_api, + OneByteCreateAPI create_api, + enum length_type length_mode) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + if (length_mode == auto_length) { + copied = NAPI_AUTO_LENGTH; + } + NODE_API_CALL(env, create_api(env, buffer, copied, &output)); + + return output; +} + +// Test passing back the two-byte string we got from JS. +static napi_value TestTwoByteImpl(napi_env env, + napi_callback_info info, + TwoByteGetAPI get_api, + TwoByteCreateAPI create_api, + enum length_type length_mode) { + napi_value args[1]; + NODE_API_CALL(env, validate_and_retrieve_single_string_arg(env, info, args)); + + char16_t buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NODE_API_CALL(env, get_api(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + if (length_mode == auto_length) { + copied = NAPI_AUTO_LENGTH; + } + NODE_API_CALL(env, create_api(env, buffer, copied, &output)); + + return output; +} + +#endif // TEST_JS_NATIVE_API_TEST_STRING_TEST_STRING_HELPERS_H_ diff --git a/tests/js-native-api/test_string/test_string_v10.c b/tests/js-native-api/test_string/test_string_v10.c new file mode 100644 index 0000000..c27ec2b --- /dev/null +++ b/tests/js-native-api/test_string/test_string_v10.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_string_helpers.h" + +static void free_string(node_api_basic_env env, void* data, void* hint) { + free(data); +} + +static napi_status create_external_latin1(napi_env env, + const char* string, + size_t length, + napi_value* result) { + napi_status status; + // Initialize to true, because that is the value we don't want. + bool copied = true; + char* string_copy; + const size_t actual_length = + (length == NAPI_AUTO_LENGTH ? strlen(string) : length); + const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); + string_copy = malloc(length_bytes); + memcpy(string_copy, string, length_bytes); + string_copy[actual_length] = 0; + + status = node_api_create_external_string_latin1( + env, string_copy, length, free_string, NULL, result, &copied); + // We do not want the string to be copied. + if (copied) { + return napi_generic_failure; + } + if (status != napi_ok) { + free(string_copy); + return status; + } + return napi_ok; +} + +// strlen for char16_t. Needed in case we're copying a string of length +// NAPI_AUTO_LENGTH. +static size_t strlen16(const char16_t* string) { + for (const char16_t* iter = string;; iter++) { + if (*iter == 0) { + return iter - string; + } + } + // We should never get here. + abort(); +} + +static napi_status create_external_utf16(napi_env env, + const char16_t* string, + size_t length, + napi_value* result) { + napi_status status; + // Initialize to true, because that is the value we don't want. + bool copied = true; + char16_t* string_copy; + const size_t actual_length = + (length == NAPI_AUTO_LENGTH ? strlen16(string) : length); + const size_t length_bytes = (actual_length + 1) * sizeof(*string_copy); + string_copy = malloc(length_bytes); + memcpy(string_copy, string, length_bytes); + string_copy[actual_length] = 0; + + status = node_api_create_external_string_utf16( + env, string_copy, length, free_string, NULL, result, &copied); + if (status != napi_ok) { + free(string_copy); + return status; + } + + return napi_ok; +} + +static napi_value TestLatin1External(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + create_external_latin1, + actual_length); +} + +static napi_value TestUtf16External(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + create_external_utf16, + actual_length); +} + +static napi_value TestLatin1ExternalAutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + create_external_latin1, + auto_length); +} + +static napi_value TestUtf16ExternalAutoLength(napi_env env, + napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + create_external_utf16, + auto_length); +} + +static napi_value TestPropertyKeyLatin1(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + actual_length); +} + +static napi_value TestPropertyKeyLatin1AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_latin1, + node_api_create_property_key_latin1, + auto_length); +} + +static napi_value TestPropertyKeyUtf8(napi_env env, napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + actual_length); +} + +static napi_value TestPropertyKeyUtf8AutoLength(napi_env env, + napi_callback_info info) { + return TestOneByteImpl(env, + info, + napi_get_value_string_utf8, + node_api_create_property_key_utf8, + auto_length); +} + +static napi_value TestPropertyKeyUtf16(napi_env env, napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + node_api_create_property_key_utf16, + actual_length); +} + +static napi_value TestPropertyKeyUtf16AutoLength(napi_env env, + napi_callback_info info) { + return TestTwoByteImpl(env, + info, + napi_get_value_string_utf16, + node_api_create_property_key_utf16, + auto_length); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("TestLatin1External", TestLatin1External), + DECLARE_NODE_API_PROPERTY("TestLatin1ExternalAutoLength", + TestLatin1ExternalAutoLength), + DECLARE_NODE_API_PROPERTY("TestUtf16External", TestUtf16External), + DECLARE_NODE_API_PROPERTY("TestUtf16ExternalAutoLength", + TestUtf16ExternalAutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1", TestPropertyKeyLatin1), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyLatin1AutoLength", + TestPropertyKeyLatin1AutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8", TestPropertyKeyUtf8), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf8AutoLength", + TestPropertyKeyUtf8AutoLength), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16", TestPropertyKeyUtf16), + DECLARE_NODE_API_PROPERTY("TestPropertyKeyUtf16AutoLength", + TestPropertyKeyUtf16AutoLength), + }; + + 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_string/test_v10.js b/tests/js-native-api/test_string/test_v10.js new file mode 100644 index 0000000..b6645ff --- /dev/null +++ b/tests/js-native-api/test_string/test_v10.js @@ -0,0 +1,63 @@ +"use strict"; + +// Tests for Node-API version >= 10 APIs: +// node_api_create_external_string_latin1/utf16 and +// node_api_create_property_key_latin1/utf8/utf16. +if (Number(napiVersion) < 10) { + skipTest(); +} + +const test_string_v10 = loadAddon("test_string_v10"); +// The insufficient buffer test case allocates a buffer of size 4, including +// the null terminator. +const kInsufficientIdx = 3; + +const asciiCases = [ + "", + "hello world", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "?!@#$%^&*()_+-=[]{}/.,<>'\"\\", +]; + +const latin1Cases = [ + { + str: "¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿", + utf8Length: 62, + utf8InsufficientIdx: 1, + }, + { + str: "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ", + utf8Length: 126, + utf8InsufficientIdx: 1, + }, +]; + +const unicodeCases = [ + { + str: "\u{2003}\u{2101}\u{2001}\u{202}\u{2011}", + utf8Length: 14, + utf8InsufficientIdx: 1, + }, +]; + +function testLatin1ExternalCases(str) { + assert.strictEqual(test_string_v10.TestLatin1External(str), str); + assert.strictEqual(test_string_v10.TestLatin1ExternalAutoLength(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyLatin1(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyLatin1AutoLength(str), str); +} + +function testUnicodeExternalCases(str) { + assert.strictEqual(test_string_v10.TestUtf16External(str), str); + assert.strictEqual(test_string_v10.TestUtf16ExternalAutoLength(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyUtf8(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyUtf8AutoLength(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyUtf16(str), str); + assert.strictEqual(test_string_v10.TestPropertyKeyUtf16AutoLength(str), str); +} + +asciiCases.forEach(testLatin1ExternalCases); +asciiCases.forEach(testUnicodeExternalCases); +latin1Cases.forEach((it) => testLatin1ExternalCases(it.str)); +latin1Cases.forEach((it) => testUnicodeExternalCases(it.str)); +unicodeCases.forEach((it) => testUnicodeExternalCases(it.str)); From f147c590578398b966434915f2bfa5ab4eb03b58 Mon Sep 17 00:00:00 2001 From: Balakrishna Avulapati Date: Sat, 11 Apr 2026 12:15:33 +0530 Subject: [PATCH 4/4] fix lint error Signed-off-by: Balakrishna Avulapati --- tests/js-native-api/test_string/test_v10.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/js-native-api/test_string/test_v10.js b/tests/js-native-api/test_string/test_v10.js index b6645ff..f46dab9 100644 --- a/tests/js-native-api/test_string/test_v10.js +++ b/tests/js-native-api/test_string/test_v10.js @@ -8,9 +8,6 @@ if (Number(napiVersion) < 10) { } const test_string_v10 = loadAddon("test_string_v10"); -// The insufficient buffer test case allocates a buffer of size 4, including -// the null terminator. -const kInsufficientIdx = 3; const asciiCases = [ "",