diff --git a/PORTING.md b/PORTING.md index bfdf01c..4faac6b 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` | Ported ✅ | Medium | diff --git a/eslint.config.js b/eslint.config.js index a569da7..c5c7e38 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,6 +23,8 @@ export default defineConfig([ mustNotCall: "readonly", gcUntil: "readonly", experimentalFeatures: "readonly", + napiVersion: "readonly", + skipTest: "readonly", }, }, rules: { 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/tests/js-native-api/test_string/CMakeLists.txt b/tests/js-native-api/test_string/CMakeLists.txt new file mode 100644 index 0000000..4a3b402 --- /dev/null +++ b/tests/js-native-api/test_string/CMakeLists.txt @@ -0,0 +1,6 @@ +add_node_api_cts_addon(test_string test_string.c test_null.c) + +# 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 new file mode 100644 index 0000000..21f8567 --- /dev/null +++ b/tests/js-native-api/test_string/test.js @@ -0,0 +1,94 @@ +"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.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.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..979fd43 --- /dev/null +++ b/tests/js-native-api/test_string/test_string.c @@ -0,0 +1,249 @@ +#include +#include // INT_MAX +#include +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" +#include "test_string_helpers.h" + +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 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 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("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("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), + }; + + init_test_null(env, exports); + + 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_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..f46dab9 --- /dev/null +++ b/tests/js-native-api/test_string/test_v10.js @@ -0,0 +1,60 @@ +"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"); + +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));