diff --git a/CMakeLists.txt b/CMakeLists.txt index 76a70d6f..b5d7a347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git GIT_TAG 1a8a5d6e95413ed14b38a6ac9419048f9a9c8009) FetchContent_Declare(AndroidExtensions - GIT_REPOSITORY https://github.com/bghgary/AndroidExtensions.git - GIT_TAG 7d88a601fda9892791e7b4e994e375e049615688) + GIT_REPOSITORY https://github.com/matthargett/AndroidExtensions.git + GIT_TAG 4ffcc4ab149a02b9ee72622eee708b2753fd2093) FetchContent_Declare(asio GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git GIT_TAG f693a3eb7fe72a5f19b975289afc4f437d373d9c) @@ -72,12 +72,49 @@ option(JSRUNTIMEHOST_POLYFILL_URL "Include JsRuntimeHost Polyfill URL and URLSea option(JSRUNTIMEHOST_POLYFILL_ABORT_CONTROLLER "Include JsRuntimeHost Polyfills AbortController and AbortSignal." ON) option(JSRUNTIMEHOST_POLYFILL_WEBSOCKET "Include JsRuntimeHost Polyfill WebSocket." ON) option(JSRUNTIMEHOST_POLYFILL_BLOB "Include JsRuntimeHost Polyfill Blob." ON) +option(JSR_ENABLE_ASAN "Enable AddressSanitizer support." OFF) + +if(JSR_ENABLE_ASAN) + message(STATUS "JSR_ENABLE_ASAN=ON (appending address sanitizer flags)") + set(JSR_ASAN_COMPILE_FLAGS "-fsanitize=address -fno-omit-frame-pointer") + foreach(var CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + if(NOT "${${var}}" MATCHES "-fsanitize=address") + set(${var} "${${var}} ${JSR_ASAN_COMPILE_FLAGS}") + endif() + set(${var} "${${var}}" CACHE STRING "" FORCE) + endforeach() + + set(JSR_ASAN_LINK_FLAGS "-fsanitize=address") + foreach(var CMAKE_EXE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS) + if(NOT "${${var}}" MATCHES "-fsanitize=address") + set(${var} "${${var}} ${JSR_ASAN_LINK_FLAGS}") + endif() + set(${var} "${${var}}" CACHE STRING "" FORCE) + endforeach() + message(STATUS "ASan compile flags: C=${CMAKE_C_FLAGS} CXX=${CMAKE_CXX_FLAGS}") + message(STATUS "ASan link flags: EXE=${CMAKE_EXE_LINKER_FLAGS} SHARED=${CMAKE_SHARED_LINKER_FLAGS}") +endif() # -------------------------------------------------- FetchContent_MakeAvailable_With_Message(arcana.cpp) set_property(TARGET arcana PROPERTY FOLDER Dependencies) +if(ANDROID) + FetchContent_GetProperties(AndroidExtensions) + if(NOT AndroidExtensions_POPULATED) + FetchContent_Populate(AndroidExtensions) + FetchContent_GetProperties(AndroidExtensions) + message(STATUS "Patching AndroidExtensions Globals.cpp in ${androidextensions_SOURCE_DIR}") + file(COPY + ${CMAKE_CURRENT_SOURCE_DIR}/patches/AndroidExtensions/Globals.cpp + DESTINATION ${androidextensions_SOURCE_DIR}/Source) + add_subdirectory(${androidextensions_SOURCE_DIR} ${androidextensions_BINARY_DIR}) + else() + add_subdirectory(${androidextensions_SOURCE_DIR} ${androidextensions_BINARY_DIR}) + endif() +endif() + if(JSRUNTIMEHOST_POLYFILL_XMLHTTPREQUEST) FetchContent_MakeAvailable_With_Message(UrlLib) set_property(TARGET UrlLib PROPERTY FOLDER Dependencies) diff --git a/Core/Node-API-JSI/Include/napi/napi.h b/Core/Node-API-JSI/Include/napi/napi.h index dbca8051..e5f0604d 100644 --- a/Core/Node-API-JSI/Include/napi/napi.h +++ b/Core/Node-API-JSI/Include/napi/napi.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -8,42 +9,6 @@ #include #include -// Copied from js_native_api_types.h (https://git.io/J8aI5) -typedef enum { - napi_default = 0, - napi_writable = 1 << 0, - napi_enumerable = 1 << 1, - napi_configurable = 1 << 2, -} napi_property_attributes; - -typedef enum { - // ES6 types (corresponds to typeof) - napi_undefined, - napi_null, - napi_boolean, - napi_number, - napi_string, - napi_symbol, - napi_object, - napi_function, - napi_external, -} napi_valuetype; - -typedef enum { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, - // JSI doesn't support bigint. - // napi_bigint64_array, - // napi_biguint64_array, -} napi_typedarray_type; - struct napi_env__ { napi_env__(facebook::jsi::Runtime& rt) : rt{rt} diff --git a/Core/Node-API/CMakeLists.txt b/Core/Node-API/CMakeLists.txt index 1e8b8611..5089d5d8 100644 --- a/Core/Node-API/CMakeLists.txt +++ b/Core/Node-API/CMakeLists.txt @@ -7,7 +7,10 @@ elseif(ANDROID) set(NAPI_JAVASCRIPT_ENGINE "V8" CACHE STRING "JavaScript engine for Node-API") elseif(UNIX) set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore" CACHE STRING "JavaScript engine for Node-API") - set(JAVASCRIPTCORE_LIBRARY "/usr/lib/x86_64-linux-gnu/libjavascriptcoregtk-4.1.so" CACHE STRING "Path to the JavaScriptCore shared library") + find_library(JAVASCRIPTCORE_LIBRARY javascriptcoregtk-4.1) + if(NOT JAVASCRIPTCORE_LIBRARY) + message(FATAL_ERROR "JavaScriptCore library not found. Please install libwebkit2gtk-4.1-dev") + endif() else() message(FATAL_ERROR "Unable to select Node-API JavaScript engine for platform") endif() diff --git a/Core/Node-API/Source/js_native_api_javascriptcore.cc b/Core/Node-API/Source/js_native_api_javascriptcore.cc index ef8127f6..b9995bda 100644 --- a/Core/Node-API/Source/js_native_api_javascriptcore.cc +++ b/Core/Node-API/Source/js_native_api_javascriptcore.cc @@ -664,6 +664,9 @@ struct napi_ref__ { CHECK_NAPI(ReferenceInfo::GetObjectId(env, _value, &_objectId)); if (_objectId == 0) { CHECK_NAPI(ReferenceInfo::Initialize(env, _value, [value = _value](ReferenceInfo* info) { + if (info->Env()->shutting_down) { + return; + } auto entry{info->Env()->active_ref_values.find(value)}; // NOTE: The finalizer callback is actually on a "sentinel" JS object that is linked to the // actual JS object we are trying to track. This means it is possible for the tracked object @@ -1563,14 +1566,28 @@ napi_status napi_call_function(napi_env env, CHECK_ARG(env, argv); } + JSObjectRef function_object = ToJSObject(env, func); + + std::vector call_args(argc + 1); + call_args[0] = ToJSValue(recv); + for (size_t i = 0; i < argc; ++i) { + call_args[i + 1] = ToJSValue(argv[i]); + } + JSValueRef exception{}; - JSValueRef return_value{JSObjectCallAsFunction( - env->context, - ToJSObject(env, func), - JSValueIsUndefined(env->context, ToJSValue(recv)) ? nullptr : ToJSObject(env, recv), - argc, - ToJSValues(argv), - &exception)}; + JSValueRef call_value{JSObjectGetProperty( + env->context, function_object, JSString("call"), &exception)}; + CHECK_JSC(env, exception); + + JSObjectRef call_object = JSValueToObject(env->context, call_value, &exception); + CHECK_JSC(env, exception); + + JSValueRef return_value{JSObjectCallAsFunction(env->context, + call_object, + function_object, + call_args.size(), + call_args.data(), + &exception)}; CHECK_JSC(env, exception); if (result != nullptr) { diff --git a/Core/Node-API/Source/js_native_api_javascriptcore.h b/Core/Node-API/Source/js_native_api_javascriptcore.h index da74596e..77bc1180 100644 --- a/Core/Node-API/Source/js_native_api_javascriptcore.h +++ b/Core/Node-API/Source/js_native_api_javascriptcore.h @@ -14,6 +14,7 @@ struct napi_env__ { napi_extended_error_info last_error{nullptr, nullptr, 0, napi_ok}; std::unordered_map active_ref_values{}; std::list strong_refs{}; + bool shutting_down{false}; JSValueRef constructor_info_symbol{}; JSValueRef function_info_symbol{}; @@ -32,6 +33,7 @@ struct napi_env__ { } ~napi_env__() { + shutting_down = true; deinit_refs(); deinit_symbol(wrapper_info_symbol); deinit_symbol(reference_info_symbol); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 2cb5d26c..a7efe8f4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(UnitTests) +add_subdirectory(NodeApi) npm(install --silent) diff --git a/Tests/NodeApi/.clang-format b/Tests/NodeApi/.clang-format new file mode 100644 index 00000000..b3fd9613 --- /dev/null +++ b/Tests/NodeApi/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never diff --git a/Tests/NodeApi/CMakeLists.txt b/Tests/NodeApi/CMakeLists.txt new file mode 100644 index 00000000..256782ae --- /dev/null +++ b/Tests/NodeApi/CMakeLists.txt @@ -0,0 +1,186 @@ +set(NODE_API_TEST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) + +option(JSR_NODE_API_BUILD_NATIVE_TESTS "Build Node-API native addon test modules" ON) + +set(JSR_NODE_API_NATIVE_TEST_DIRS + 2_function_arguments + 3_callbacks + 4_object_factory + 5_function_factory +) + +function(node_api_copy_test_sources TARGET_NAME) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${NODE_API_TEST_ROOT}/test + $/test + COMMENT "Copying Node-API test assets for ${TARGET_NAME}" + ) +endfunction() + +if(ANDROID) + set(NODE_LITE_PLATFORM_SRC node_lite_posix.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC child_process_posix.cpp) +elseif(APPLE) + set(NODE_LITE_PLATFORM_SRC node_lite_posix.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC child_process_posix.cpp) +elseif(WIN32) + set(NODE_LITE_PLATFORM_SRC node_lite_windows.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC child_process.cpp) +else() + set(NODE_LITE_PLATFORM_SRC node_lite_posix.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC child_process_posix.cpp) + message(WARNING "Node-API node_lite platform not yet customized for ${CMAKE_SYSTEM_NAME}; using POSIX defaults.") +endif() + +add_executable(node_lite + ${NODE_LITE_CHILD_PROCESS_SRC} + child_process.h + compat.h + js_runtime_api.cpp + js_runtime_api.h + node_lite.cpp + node_lite.h + node_lite_jsruntimehost.cpp + ${NODE_LITE_PLATFORM_SRC} + string_utils.cpp + string_utils.h +) + +target_include_directories(node_lite + PRIVATE + ${NODE_API_TEST_ROOT} + ${NODE_API_TEST_ROOT}/include + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Shared + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Engine/${NAPI_JAVASCRIPT_ENGINE} + ${CMAKE_SOURCE_DIR}/Core/Node-API/Source +) + +target_compile_definitions(node_lite + PRIVATE + NODE_API_EXPERIMENTAL_NO_WARNING +) + +target_link_libraries(node_lite + PRIVATE + napi +) + +node_api_copy_test_sources(node_lite) + +add_executable(NodeApiTests + ${NODE_LITE_CHILD_PROCESS_SRC} + child_process.h + string_utils.cpp + string_utils.h + test_basics.cpp + test_main.cpp + main.cpp + test_main.h +) + +target_include_directories(NodeApiTests + PRIVATE + ${NODE_API_TEST_ROOT} + ${NODE_API_TEST_ROOT}/include +) + +target_link_libraries(NodeApiTests + PRIVATE + gtest_main +) + +node_api_copy_test_sources(NodeApiTests) + +add_dependencies(NodeApiTests node_lite) + +add_custom_target(NodeApiModules) + +# Always define which tests are available, regardless of whether we build the native modules +list(JOIN JSR_NODE_API_NATIVE_TEST_DIRS "," NODE_API_NATIVE_TESTS_STRING) +target_compile_definitions(node_lite + PRIVATE + NODE_API_AVAILABLE_NATIVE_TESTS=\"${NODE_API_NATIVE_TESTS_STRING}\" +) +target_compile_definitions(NodeApiTests + PRIVATE + NODE_API_AVAILABLE_NATIVE_TESTS=\"${NODE_API_NATIVE_TESTS_STRING}\" +) + +# Only define NODE_API_TESTS_HAVE_NATIVE_MODULES when actually building native modules +if(JSR_NODE_API_BUILD_NATIVE_TESTS) + target_compile_definitions(node_lite + PRIVATE + NODE_API_TESTS_HAVE_NATIVE_MODULES=1 + ) + target_compile_definitions(NodeApiTests + PRIVATE + NODE_API_TESTS_HAVE_NATIVE_MODULES=1 + ) +endif() + +function(add_node_api_module MODULE_TARGET) + cmake_parse_arguments(PARSE_ARGV 0 ARG "" "" "SOURCES;DEFINES") + + get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) + + if(NOT "${MODULE_TARGET}" STREQUAL "${FOLDER_NAME}") + set(MODULE_TARGET "${FOLDER_NAME}_${MODULE_TARGET}") + endif() + + add_library(${MODULE_TARGET} MODULE) + target_sources(${MODULE_TARGET} PRIVATE ${ARG_SOURCES}) + target_include_directories(${MODULE_TARGET} + PRIVATE + ${NODE_API_TEST_ROOT}/include + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Shared + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Shared/napi + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Engine/${NAPI_JAVASCRIPT_ENGINE} + ${CMAKE_SOURCE_DIR}/Core/Node-API/Include/Engine/${NAPI_JAVASCRIPT_ENGINE}/napi + ) + + target_compile_definitions(${MODULE_TARGET} + PRIVATE + NODE_API_EXPERIMENTAL_NO_WARNING + NODE_GYP_MODULE_NAME=\"${FOLDER_NAME}\" + ${ARG_DEFINES} + ) + + if(APPLE) + target_link_options(${MODULE_TARGET} + PRIVATE + "-undefined" "dynamic_lookup" + ) + endif() + + set(MODULE_OUTPUT_DIR + ${CMAKE_CURRENT_BINARY_DIR}/build/$,Debug,Release>) + set_target_properties(${MODULE_TARGET} + PROPERTIES + PREFIX "" + SUFFIX ".node" + ARCHIVE_OUTPUT_DIRECTORY ${MODULE_OUTPUT_DIR} + LIBRARY_OUTPUT_DIRECTORY ${MODULE_OUTPUT_DIR} + RUNTIME_OUTPUT_DIRECTORY ${MODULE_OUTPUT_DIR} + ) + + add_dependencies(NodeApiModules ${MODULE_TARGET}) + + add_custom_command(TARGET ${MODULE_TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + $/test/js-native-api/${FOLDER_NAME}/build + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_BINARY_DIR}/build + $/test/js-native-api/${FOLDER_NAME}/build + COMMAND ${CMAKE_COMMAND} -E make_directory + $/test/js-native-api/${FOLDER_NAME}/build + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_BINARY_DIR}/build + $/test/js-native-api/${FOLDER_NAME}/build + COMMENT "Copying Node-API module ${MODULE_TARGET} outputs" + ) +endfunction() + +add_dependencies(NodeApiTests NodeApiModules) + +add_subdirectory(test) diff --git a/Tests/NodeApi/child_process.cpp b/Tests/NodeApi/child_process.cpp new file mode 100644 index 00000000..2fd22cd8 --- /dev/null +++ b/Tests/NodeApi/child_process.cpp @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// Windows-specific implementation of the `spawnSync` function for creating +// child processes and capturing their output. This code is designed to work +// with the Windows API and is not portable to other platforms. It uses pipes +// to redirect the standard output and error streams of the child process back +// to the parent process, allowing the parent to read the output and error +// messages generated by the child process. +// +// The `spawnSync` function takes a command and a list of arguments, creates a +// child process to execute the command, and returns a `ProcessResult` structure +// containing the exit status and the captured output and error messages. +// + +#include "child_process.h" + +#include +#include +#include +#include +#include "string_utils.h" + +#ifndef VerifyElseExit +#define VerifyElseExit(condition) \ + do { \ + if (!(condition)) { \ + ExitOnError(#condition); \ + } \ + } while (false) +#endif + +namespace node_api_tests { + +namespace { + +std::string ReadFromPipe(HANDLE pipeHandle); +void ExitOnError(const char* message); + +struct AutoHandle { + HANDLE handle{NULL}; + + AutoHandle() = default; + AutoHandle(HANDLE handle) : handle(handle) {} + ~AutoHandle() { ::CloseHandle(handle); } + + AutoHandle(const AutoHandle&) = delete; + AutoHandle& operator=(const AutoHandle&) = delete; + + void Close() { + ::CloseHandle(handle); + handle = NULL; + } +}; +} // namespace + +// Create a child process that uses the previously created pipes for STDIN and +// STDOUT. +ProcessResult SpawnSync(std::string_view command, + std::vector args) { + ProcessResult result{}; + + // Set the bInheritHandle flag so pipe handles are inherited. + + SECURITY_ATTRIBUTES handles_are_inheritable = { + sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + + AutoHandle out_read_handle, out_write_handle; + VerifyElseExit(CreatePipe(&out_read_handle.handle, + &out_write_handle.handle, + &handles_are_inheritable, + 0)); + // Ensure the read handle to the pipe for STDOUT is not inherited. + VerifyElseExit( + SetHandleInformation(out_read_handle.handle, HANDLE_FLAG_INHERIT, 0)); + + AutoHandle err_read_handle, err_write_handle; + VerifyElseExit(CreatePipe(&err_read_handle.handle, + &err_write_handle.handle, + &handles_are_inheritable, + 0)); + // Ensure the read handle to the pipe for STDERR is not inherited. + VerifyElseExit( + SetHandleInformation(err_read_handle.handle, HANDLE_FLAG_INHERIT, 0)); + + // Set up members of the STARTUPINFO structure. + // This structure specifies the STDIN and STDOUT handles for redirection. + STARTUPINFOA startup_info{}; + startup_info.cb = sizeof(STARTUPINFOA); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = out_write_handle.handle; + startup_info.hStdError = err_write_handle.handle; + + // Create the child process. + + std::string commandLine = std::string(command); + for (std::string& arg : args) { + commandLine += " " + arg; + } + PROCESS_INFORMATION process_info{}; + VerifyElseExit( + CreateProcessA(nullptr, + const_cast(commandLine.c_str()), // command line + nullptr, // process security attributes + nullptr, // primary thread security attributes + TRUE, // handles are inherited + CREATE_DEFAULT_ERROR_MODE, // creation flags + nullptr, // use parent's environment + nullptr, // use parent's current directory + &startup_info, // STARTUPINFO pointer + &process_info)); // receives PROCESS_INFORMATION + + VerifyElseExit(WAIT_OBJECT_0 == + ::WaitForSingleObject(process_info.hProcess, INFINITE)); + + DWORD exit_code; + VerifyElseExit(::GetExitCodeProcess(process_info.hProcess, &exit_code)); + + // Close handles to the child process and its primary thread. + // Some applications might keep these handles to monitor the status + // of the child process, for example. + ::CloseHandle(process_info.hProcess); + ::CloseHandle(process_info.hThread); + + // Close handles to the stdin and stdout pipes no longer needed by the child + // process. If they are not explicitly closed, there is no way to recognize + // that the child process has ended. + + out_write_handle.Close(); + err_write_handle.Close(); + + result.status = exit_code; + result.std_output = + ReplaceAll(ReadFromPipe(out_read_handle.handle), "\r\n", "\n"); + result.std_error = + ReplaceAll(ReadFromPipe(err_read_handle.handle), "\r\n", "\n"); + + return result; +} + +namespace { +std::string ReadFromPipe(HANDLE pipeHandle) { + std::string result; + constexpr size_t bufferSize = 4096; + char buffer[bufferSize]; + + for (;;) { + DWORD bytesRead; + BOOL isSuccess = + ::ReadFile(pipeHandle, buffer, bufferSize, &bytesRead, nullptr); + if (!isSuccess || bytesRead == 0) break; + + result.append(buffer, bytesRead); + } + + return result; +} + +// Format a readable error message, display a message box, +// and exit from the application. +void ExitOnError(const char* message) { + LPVOID lpMsgBuf; + LPVOID lpDisplayBuf; + DWORD dw = GetLastError(); + + ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMsgBuf, + 0, + nullptr); + + lpDisplayBuf = (LPVOID)LocalAlloc( + LMEM_ZEROINIT, (lstrlenA((LPCSTR)lpMsgBuf) + lstrlenA(message) + 40)); + ::StringCchPrintfA((LPSTR)lpDisplayBuf, + LocalSize(lpDisplayBuf), + "%s failed with error %d: %s", + message, + dw, + lpMsgBuf); + fprintf(stderr, "%s\n", (const char*)lpDisplayBuf); + + ::LocalFree(lpMsgBuf); + ::LocalFree(lpDisplayBuf); + ::ExitProcess(1); +} + +} // namespace +} // namespace node_api_tests \ No newline at end of file diff --git a/Tests/NodeApi/child_process.h b/Tests/NodeApi/child_process.h new file mode 100644 index 00000000..aed58908 --- /dev/null +++ b/Tests/NodeApi/child_process.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef NODE_API_TEST_CHILD_PROCESS_H +#define NODE_API_TEST_CHILD_PROCESS_H + +#include +#include +#include + +namespace node_api_tests { + +// Struct to hold the result of a child process execution. +struct ProcessResult { + uint32_t status; // Exit status of the child process. + std::string std_output; // Standard output from the child process. + std::string std_error; // Standard error from the child process. +}; + +// Creates a child process to run the given command with the specified +// arguments. +ProcessResult SpawnSync(std::string_view command, + std::vector args); + +} // namespace node_api_tests + +#endif // !NODE_API_TEST_CHILD_PROCESS_H \ No newline at end of file diff --git a/Tests/NodeApi/child_process_android.cpp b/Tests/NodeApi/child_process_android.cpp new file mode 100644 index 00000000..cb70ccd1 --- /dev/null +++ b/Tests/NodeApi/child_process_android.cpp @@ -0,0 +1,14 @@ +#include "child_process.h" + +namespace node_api_tests { + +ProcessResult SpawnSync(std::string_view /*command*/, std::vector /*args*/) +{ + ProcessResult result{}; + result.status = -1; + result.std_error = "child_process.spawnSync is not supported on this platform."; + result.std_output.clear(); + return result; +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/child_process_posix.cpp b/Tests/NodeApi/child_process_posix.cpp new file mode 100644 index 00000000..bc461b0e --- /dev/null +++ b/Tests/NodeApi/child_process_posix.cpp @@ -0,0 +1,189 @@ +#include "child_process.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__ANDROID__) +#include +#endif + +#if !defined(__ANDROID__) +#include +#elif (__ANDROID_API__ >= 29) +#include +#endif + +#ifndef VerifyElseExit +#define VerifyElseExit(condition) \ + do { \ + if (!(condition)) { \ + ExitOnError(#condition, nullptr); \ + } \ + } while (false) +#endif + +#ifndef VerifyElseExitWithCleanup +#define VerifyElseExitWithCleanup(condition, actions_ptr) \ + do { \ + if (!(condition)) { \ + ExitOnError(#condition, actions_ptr); \ + } \ + } while (false) +#endif + +#if defined(__ANDROID__) && (__ANDROID_API__ < 29) + +namespace node_api_tests { + +ProcessResult SpawnSync(std::string_view /*command*/, + std::vector /*args*/) { + ProcessResult result{}; + result.status = -1; + result.std_error = "child_process.spawnSync is not supported on this platform."; + result.std_output.clear(); + return result; +} + +} // namespace node_api_tests + +#else + +extern char** environ; + +namespace node_api_tests { + +namespace { + +std::string ReadFromFd(int fd); +void ExitOnError(const char* message, posix_spawn_file_actions_t* actions); + +} // namespace + +ProcessResult SpawnSync(std::string_view command, + std::vector args) { + ProcessResult result{}; + + // These int arrays each comprise two file descriptors: { readEnd, writeEnd }. + int stdout_pipe[2], stderr_pipe[2]; + VerifyElseExit(pipe(stdout_pipe) == 0); + VerifyElseExit(pipe(stderr_pipe) == 0); + + posix_spawn_file_actions_t actions; + VerifyElseExit(posix_spawn_file_actions_init(&actions) == 0); + + VerifyElseExitWithCleanup(posix_spawn_file_actions_adddup2( + &actions, stdout_pipe[1], STDOUT_FILENO) == 0, + &actions); + VerifyElseExitWithCleanup(posix_spawn_file_actions_adddup2( + &actions, stderr_pipe[1], STDERR_FILENO) == 0, + &actions); + + VerifyElseExitWithCleanup( + posix_spawn_file_actions_addclose(&actions, stdout_pipe[0]) == 0, + &actions); + VerifyElseExitWithCleanup( + posix_spawn_file_actions_addclose(&actions, stderr_pipe[0]) == 0, + &actions); + + std::vector argv; + argv.push_back(strdup(std::string(command).c_str())); + for (const std::string& arg : args) { + argv.push_back(strdup(arg.c_str())); + } + argv.push_back(nullptr); + + pid_t pid; + VerifyElseExitWithCleanup( + posix_spawnp(&pid, argv[0], &actions, nullptr, argv.data(), environ) == 0, + &actions); + + posix_spawn_file_actions_destroy(&actions); + + // Close the write ends of the pipes. + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + int wait_status; + pid_t waited_pid; + do { + waited_pid = waitpid(pid, &wait_status, 0); + } while (waited_pid == -1 && errno == EINTR); + + VerifyElseExit(waited_pid == pid); + + if (WIFEXITED(wait_status)) { + result.status = WEXITSTATUS(wait_status); + } else if (WIFSIGNALED(wait_status)) { + result.status = 128 + WTERMSIG(wait_status); + } else { + result.status = 1; + } + result.std_output = ReadFromFd(stdout_pipe[0]); + result.std_error = ReadFromFd(stderr_pipe[0]); + + // Close the read ends of the pipes. + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + for (char* arg : argv) { + free(arg); + } + + return result; +} + +namespace { + +std::string ReadFromFd(int fd) { + std::string result; + constexpr size_t bufferSize = 4096; + char buffer[bufferSize]; + ssize_t bytesRead; + while (true) { + bytesRead = read(fd, buffer, bufferSize); + if (bytesRead > 0) { + result.append(buffer, bytesRead); + continue; + } + + if (bytesRead == 0) { + break; + } + + if (errno == EINTR) { + continue; + } + + ExitOnError("read", nullptr); + } + return result; +} + +// Format a readable error message, print it to console, and exit from the +// application. +void ExitOnError(const char* message, posix_spawn_file_actions_t* actions) { + int err = errno; + const char* err_msg = strerror(err); + + fprintf(stderr, "%s failed with error %d: %s\n", message, err, err_msg); + + if (actions != nullptr) { + posix_spawn_file_actions_destroy(actions); + } + + exit(1); +} + +} // namespace + +} // namespace node_api_tests + +#endif // __ANDROID__ diff --git a/Tests/NodeApi/compat.h b/Tests/NodeApi/compat.h new file mode 100644 index 00000000..7974b34c --- /dev/null +++ b/Tests/NodeApi/compat.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#ifndef SRC_PUBLIC_COMPAT_H_ +#define SRC_PUBLIC_COMPAT_H_ + +// This file contains some useful datatypes recently introduced in C++17 and +// C++20. They must be removed after we switch the toolset to the newer C++ +// language version. + +#include +#ifdef __cpp_lib_span +#include +#endif + +namespace node_api_tests { + +#ifdef __cpp_lib_span +using std::span; +#else +/** + * @brief A span of values that can be used to pass arguments to function. + * + * For C++20 we should consider to replace it with std::span. + */ +template +struct span { + constexpr span(std::initializer_list il) noexcept + : data_{const_cast(il.begin())}, size_{il.size()} {} + constexpr span(T* data, size_t size) noexcept : data_{data}, size_{size} {} + + [[nodiscard]] constexpr T* data() const noexcept { return data_; } + + [[nodiscard]] constexpr size_t size() const noexcept { return size_; } + + [[nodiscard]] constexpr T* begin() const noexcept { return data_; } + + [[nodiscard]] constexpr T* end() const noexcept { return data_ + size_; } + + const T& operator[](size_t index) const noexcept { return *(data_ + index); } + + private: + T* data_; + size_t size_; +}; +#endif // __cpp_lib_span + +} // namespace node_api_tests + +#endif // SRC_PUBLIC_COMPAT_H_ diff --git a/Tests/NodeApi/include/node_api.h b/Tests/NodeApi/include/node_api.h new file mode 100644 index 00000000..0ba4bc6e --- /dev/null +++ b/Tests/NodeApi/include/node_api.h @@ -0,0 +1,67 @@ +#ifndef NODE_API_H_ +#define NODE_API_H_ + +#include +#include "node_api_types.h" + +#ifdef __cplusplus +#define NODE_API_EXTERN_C_START extern "C" { +#define NODE_API_EXTERN_C_END } +#else +#define NODE_API_EXTERN_C_START +#define NODE_API_EXTERN_C_END +#endif + +#ifdef _WIN32 +#define NAPI_MODULE_EXPORT __declspec(dllexport) +#else +#define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) +#endif + +#ifndef NAPI_MODULE_VERSION +#define NAPI_MODULE_VERSION 1 +#endif + +typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env, + napi_value exports); + +typedef struct napi_module_s { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NODE_API_MODULE_GET_API_VERSION_FUNCTION node_api_module_get_api_version_v1 +#define NODE_API_MODULE_REGISTER_FUNCTION napi_register_module_v1 + +#define NAPI_MODULE_INIT() \ + NODE_API_EXTERN_C_START \ + NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION_FUNCTION(void) {\ + return NAPI_VERSION; \ + } \ + NAPI_MODULE_EXPORT napi_value NODE_API_MODULE_REGISTER_FUNCTION( \ + napi_env env, napi_value exports); \ + NODE_API_EXTERN_C_END \ + static napi_value napi_module_init_impl(napi_env env, napi_value exports); \ + NODE_API_EXTERN_C_START \ + NAPI_MODULE_EXPORT napi_value NODE_API_MODULE_REGISTER_FUNCTION( \ + napi_env env, napi_value exports) { \ + return napi_module_init_impl(env, exports); \ + } \ + NODE_API_EXTERN_C_END \ + static napi_value napi_module_init_impl(napi_env env, napi_value exports) + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_INIT() { \ + (void)(modname); \ + return regfunc(env, exports); \ + } + +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + NAPI_MODULE(modname, regfunc) + +#endif // NODE_API_H_ diff --git a/Tests/NodeApi/include/node_api_types.h b/Tests/NodeApi/include/node_api_types.h new file mode 100644 index 00000000..97a2fb22 --- /dev/null +++ b/Tests/NodeApi/include/node_api_types.h @@ -0,0 +1,24 @@ +#ifndef NODE_API_TYPES_H_ +#define NODE_API_TYPES_H_ + +#include + +typedef struct napi_callback_scope__* napi_callback_scope; +typedef struct napi_async_context__* napi_async_context; +typedef struct napi_async_work__* napi_async_work; + +typedef void(NAPI_CDECL* napi_async_execute_callback)(napi_env env, + void* data); +typedef void(NAPI_CDECL* napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#endif // NODE_API_TYPES_H_ + diff --git a/Tests/NodeApi/js_runtime_api.cpp b/Tests/NodeApi/js_runtime_api.cpp new file mode 100644 index 00000000..c03740ac --- /dev/null +++ b/Tests/NodeApi/js_runtime_api.cpp @@ -0,0 +1,112 @@ +#include "js_runtime_api.h" + +#include +#include + +#if defined(__APPLE__) +#include +#include "js_native_api_javascriptcore.h" +#elif defined(__ANDROID__) +#include +#include "js_native_api_v8.h" +#endif + +struct jsr_napi_env_scope_s { + napi_env env{nullptr}; +}; + +napi_status jsr_open_napi_env_scope(napi_env env, + jsr_napi_env_scope* scope) { + if (scope == nullptr) { + return napi_invalid_arg; + } + + auto* scope_impl = new jsr_napi_env_scope_s{}; + scope_impl->env = env; + *scope = scope_impl; + return napi_ok; +} + +napi_status jsr_close_napi_env_scope(napi_env /*env*/, + jsr_napi_env_scope scope) { + if (scope == nullptr) { + return napi_invalid_arg; + } + + delete scope; + return napi_ok; +} + +napi_status jsr_run_script(napi_env env, + napi_value source, + const char* source_url, + napi_value* result) { + return napi_run_script(env, source, source_url, result); +} + +napi_status jsr_collect_garbage(napi_env env) { +#if defined(__APPLE__) + if (env == nullptr) { + return napi_invalid_arg; + } + + JSGlobalContextRef context = env->context; + if (context == nullptr) { + return napi_invalid_arg; + } + + JSGarbageCollect(context); + return napi_ok; +#elif defined(__ANDROID__) + if (env == nullptr) { + return napi_invalid_arg; + } + + v8::Isolate* isolate = env->isolate; + if (isolate == nullptr) { + return napi_invalid_arg; + } + + isolate->RequestGarbageCollectionForTesting( + v8::Isolate::kFullGarbageCollection); + return napi_ok; +#else + (void)env; + return napi_generic_failure; +#endif +} + +napi_status jsr_initialize_native_module( + napi_env env, + napi_addon_register_func register_module, + int32_t /*api_version*/, + napi_value* exports) { + if (env == nullptr || register_module == nullptr || exports == nullptr) { + return napi_invalid_arg; + } + + napi_value module_exports{}; + napi_status status = napi_create_object(env, &module_exports); + if (status != napi_ok) { + return status; + } + + napi_value returned_exports = register_module(env, module_exports); + + bool has_exception = false; + status = napi_is_exception_pending(env, &has_exception); + if (status != napi_ok) { + return status; + } + + if (has_exception) { + return napi_pending_exception; + } + + if (returned_exports != nullptr && returned_exports != module_exports) { + module_exports = returned_exports; + } + + *exports = module_exports; + return napi_ok; +} diff --git a/Tests/NodeApi/js_runtime_api.h b/Tests/NodeApi/js_runtime_api.h new file mode 100644 index 00000000..27b39461 --- /dev/null +++ b/Tests/NodeApi/js_runtime_api.h @@ -0,0 +1,219 @@ +#ifndef HERMES_JS_RUNTIME_API_H +#define HERMES_JS_RUNTIME_API_H + +#include "node_api.h" + +// +// Node-API extensions required for JavaScript engine hosting. +// +// It is a very early version of the APIs which we consider to be experimental. +// These APIs are not stable yet and are subject to change while we continue +// their development. After some time we will stabilize the APIs and make them +// "officially stable". +// + +#define JSR_API NAPI_EXTERN napi_status NAPI_CDECL + +EXTERN_C_START + +typedef struct jsr_runtime_s *jsr_runtime; +typedef struct jsr_config_s *jsr_config; +typedef struct jsr_prepared_script_s *jsr_prepared_script; +typedef struct jsr_napi_env_scope_s *jsr_napi_env_scope; + +typedef void(NAPI_CDECL *jsr_data_delete_cb)(void *data, void *deleter_data); + +//============================================================================= +// jsr_runtime +//============================================================================= + +JSR_API jsr_create_runtime(jsr_config config, jsr_runtime *runtime); +JSR_API jsr_delete_runtime(jsr_runtime runtime); +JSR_API jsr_runtime_get_node_api_env(jsr_runtime runtime, napi_env *env); + +//============================================================================= +// jsr_config +//============================================================================= + +JSR_API jsr_create_config(jsr_config *config); +JSR_API jsr_delete_config(jsr_config config); + +JSR_API jsr_config_enable_inspector(jsr_config config, bool value); +JSR_API jsr_config_set_inspector_runtime_name( + jsr_config config, + const char *name); +JSR_API jsr_config_set_inspector_port(jsr_config config, uint16_t port); +JSR_API jsr_config_set_inspector_break_on_start(jsr_config config, bool value); + +JSR_API jsr_config_enable_gc_api(jsr_config config, bool value); + +JSR_API jsr_config_set_explicit_microtasks(jsr_config config, bool value); + +// A callback to process unhandled JS error +typedef void(NAPI_CDECL *jsr_unhandled_error_cb)( + void *cb_data, + napi_env env, + napi_value error); + +JSR_API jsr_config_on_unhandled_error( + jsr_config config, + void *cb_data, + jsr_unhandled_error_cb unhandled_error_cb); + +//============================================================================= +// jsr_config task runner +//============================================================================= + +// A callback to run task +typedef void(NAPI_CDECL *jsr_task_run_cb)(void *task_data); + +// A callback to post task to the task runner +typedef void(NAPI_CDECL *jsr_task_runner_post_task_cb)( + void *task_runner_data, + void *task_data, + jsr_task_run_cb task_run_cb, + jsr_data_delete_cb task_data_delete_cb, + void *deleter_data); + +JSR_API jsr_config_set_task_runner( + jsr_config config, + void *task_runner_data, + jsr_task_runner_post_task_cb task_runner_post_task_cb, + jsr_data_delete_cb task_runner_data_delete_cb, + void *deleter_data); + +//============================================================================= +// jsr_config script cache +//============================================================================= + +typedef void(NAPI_CDECL *jsr_script_cache_load_cb)( + void *script_cache_data, + const char *source_url, + uint64_t source_hash, + const char *runtime_name, + uint64_t runtime_version, + const char *cache_tag, + const uint8_t **buffer, + size_t *buffer_size, + jsr_data_delete_cb *buffer_delete_cb, + void **deleter_data); + +typedef void(NAPI_CDECL *jsr_script_cache_store_cb)( + void *script_cache_data, + const char *source_url, + uint64_t source_hash, + const char *runtime_name, + uint64_t runtime_version, + const char *cache_tag, + const uint8_t *buffer, + size_t buffer_size, + jsr_data_delete_cb buffer_delete_cb, + void *deleter_data); + +JSR_API jsr_config_set_script_cache( + jsr_config config, + void *script_cache_data, + jsr_script_cache_load_cb script_cache_load_cb, + jsr_script_cache_store_cb script_cache_store_cb, + jsr_data_delete_cb script_cache_data_delete_cb, + void *deleter_data); + +//============================================================================= +// napi_env scope +//============================================================================= + +// Opens the napi_env scope in the current thread. +// Calling Node-API functions without the opened scope may cause a failure. +// The scope must be closed by the jsr_close_napi_env_scope call. +JSR_API jsr_open_napi_env_scope(napi_env env, jsr_napi_env_scope *scope); + +// Closes the napi_env scope in the current thread. It must match to the +// jsr_open_napi_env_scope call. +JSR_API jsr_close_napi_env_scope(napi_env env, jsr_napi_env_scope scope); + +//============================================================================= +// Additional functions to implement JSI +//============================================================================= + +// To implement JSI description() +JSR_API jsr_get_description(napi_env env, const char **result); + +// To implement JSI queueMicrotask() +JSR_API jsr_queue_microtask(napi_env env, napi_value callback); + +// To implement JSI drainMicrotasks() +JSR_API +jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool *result); + +// To implement JSI isInspectable() +JSR_API jsr_is_inspectable(napi_env env, bool *result); + +//============================================================================= +// Script preparing and running. +// +// Script is usually converted to byte code, or in other words - prepared - for +// execution. Then, we can run the prepared script. +//============================================================================= + +// Run script with source URL. +JSR_API jsr_run_script( + napi_env env, + napi_value source, + const char *source_url, + napi_value *result); + +// Prepare the script for running. +JSR_API jsr_create_prepared_script( + napi_env env, + const uint8_t *script_data, + size_t script_length, + jsr_data_delete_cb script_delete_cb, + void *deleter_data, + const char *source_url, + jsr_prepared_script *result); + +// Delete the prepared script. +JSR_API jsr_delete_prepared_script( + napi_env env, + jsr_prepared_script prepared_script); + +// Run the prepared script. +JSR_API jsr_prepared_script_run( + napi_env env, + jsr_prepared_script prepared_script, + napi_value *result); + +//============================================================================= +// Functions to support unit tests. +//============================================================================= + +// Provides a hint to run garbage collection. +// It is typically used for unit tests. +// It requires enabling GC by calling jsr_config_enable_gc_api. +JSR_API jsr_collect_garbage(napi_env env); + +// Checks if the environment has an unhandled promise rejection. +JSR_API jsr_has_unhandled_promise_rejection(napi_env env, bool *result); + +// Gets and clears the last unhandled promise rejection. +JSR_API jsr_get_and_clear_last_unhandled_promise_rejection( + napi_env env, + napi_value *result); + +// Create new napi_env for the runtime. +JSR_API +jsr_create_node_api_env(napi_env root_env, int32_t api_version, napi_env *env); + +// Run task in the environment context. +JSR_API jsr_run_task(napi_env env, jsr_task_run_cb task_cb, void *data); + +// Initializes native module. +JSR_API jsr_initialize_native_module( + napi_env env, + napi_addon_register_func register_module, + int32_t api_version, + napi_value *exports); + +EXTERN_C_END + +#endif // HERMES_JS_RUNTIME_API_H \ No newline at end of file diff --git a/Tests/NodeApi/main.cpp b/Tests/NodeApi/main.cpp new file mode 100644 index 00000000..1565693c --- /dev/null +++ b/Tests/NodeApi/main.cpp @@ -0,0 +1,81 @@ +#include "test_main.h" + +#include + +#include +#include +#include +#include + +#include "child_process.h" + +namespace fs = std::filesystem; + +namespace { + +fs::path ResolveNodeLitePath(const fs::path& exe_path) { + fs::path nodeLitePath = exe_path; + nodeLitePath.replace_filename("node_lite"); +#if defined(_WIN32) + nodeLitePath += ".exe"; +#endif + return nodeLitePath; +} + +fs::path ResolveTestsRoot(const fs::path& exe_path) { + fs::path testRootPath = exe_path.parent_path(); + fs::path js_root = testRootPath / "test"; + if (!fs::exists(js_root)) { + testRootPath = testRootPath.parent_path(); + js_root = testRootPath / "test"; + } + return js_root; +} + +std::unordered_set ParseEnabledNativeSuites() { + std::unordered_set suites; +#ifdef NODE_API_AVAILABLE_NATIVE_TESTS + std::stringstream stream(NODE_API_AVAILABLE_NATIVE_TESTS); + std::string entry; + while (std::getline(stream, entry, ',')) { + if (!entry.empty()) { + suites.insert(entry); + } + } +#endif + return suites; +} + +} // namespace + +int main(int argc, char** argv) { + fs::path exe_path = fs::canonical(argv[0]); + fs::path js_root = ResolveTestsRoot(exe_path); + if (!fs::exists(js_root)) { + std::cerr << "Error: Cannot find Node-API test directory." << std::endl; + return EXIT_FAILURE; + } + + fs::path node_lite_path = ResolveNodeLitePath(exe_path); + if (!fs::exists(node_lite_path)) { + std::cerr << "Error: Cannot find node_lite executable at " + << node_lite_path << std::endl; + return EXIT_FAILURE; + } + + node_api_tests::NodeApiTestConfig config{}; + config.js_root = js_root; + config.run_script = + [node_lite_path](const fs::path& script_path) + -> node_api_tests::ProcessResult { + return node_api_tests::SpawnSync(node_lite_path.string(), + {script_path.string()}); + }; + config.enabled_native_suites = ParseEnabledNativeSuites(); + + node_api_tests::InitializeNodeApiTests(config); + + ::testing::InitGoogleTest(&argc, argv); + node_api_tests::RegisterNodeApiTests(); + return RUN_ALL_TESTS(); +} diff --git a/Tests/NodeApi/node_lite.cpp b/Tests/NodeApi/node_lite.cpp new file mode 100644 index 00000000..e7e6ffaf --- /dev/null +++ b/Tests/NodeApi/node_lite.cpp @@ -0,0 +1,1425 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "node_lite.h" +#include "js_runtime_api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "child_process.h" + +namespace fs = std::filesystem; + +namespace node_api_tests { + +namespace { + +std::mutex& ErrorHandlerMutex() { + static std::mutex mutex; + return mutex; +} + +void DefaultFatalErrorHandler(const NodeLiteFatalErrorInfo& info) { + if (!info.message.empty()) { + std::cerr << info.message; + if (!info.details.empty()) { + std::cerr << '\n' << info.details; + } + std::cerr << std::endl; + } else if (!info.details.empty()) { + std::cerr << info.details << std::endl; + } + std::exit(info.exit_code); +} + +NodeApiRef MakeNodeApiRef(napi_env env, napi_value value) { + napi_ref ref{}; + NODE_LITE_CALL(napi_create_reference(env, value, 1, &ref)); + return NodeApiRef(ref, NodeApiRefDeleter(env)); +} + +template +void ThrowJSErrorOnException(napi_env env, TCallback&& callback) noexcept { + try { + callback(); + } catch (const NodeLiteException& e) { + if (e.error_status() == napi_pending_exception) { + napi_value error = NodeApi::GetAndClearLastException(env); + NodeApi::ThrowError(env, error); + } else { + NodeApi::ThrowError(env, e.what()); + } + } catch (const std::exception& e) { + NodeApi::ThrowError(env, e.what()); + } +} + +template +void ExitOnException(napi_env env, TCallback&& callback) noexcept { + try { + callback(); + } catch (const NodeLiteException& e) { + if (e.error_status() == napi_pending_exception) { + napi_value error = NodeApi::GetAndClearLastException(env); + NodeLiteErrorHandler::ExitWithJSError(env, error); + } else { + NodeLiteErrorHandler::ExitWithMessage(e.what()); + } + } catch (const std::exception& e) { + NodeLiteErrorHandler::ExitWithMessage(e.what()); + } +} + +std::string ReadFileText(napi_env env, fs::path file_path) { + std::ifstream file_stream(file_path.string()); + NODE_LITE_ASSERT(file_stream.is_open(), + "Failed to open file: %s. Error: %s", + file_path.c_str(), + std::strerror(errno)); + std::ostringstream ss; + ss << file_stream.rdbuf(); + return ss.str(); +} + +class NodeApiCallbackInfo { + public: + NodeApiCallbackInfo(napi_env env, napi_callback_info info) { + size_t argc{inline_args_.size()}; + napi_value* argv = inline_args_.data(); + NODE_LITE_CALL( + napi_get_cb_info(env, info, &argc, argv, &this_arg_, &data_)); + if (argc > inline_args_.size()) { + dynamic_args_ = std::make_unique(argc); + argv = dynamic_args_.get(); + NODE_LITE_CALL( + napi_get_cb_info(env, info, &argc, argv, &this_arg_, &data_)); + } + args_ = span(argv, argc); + } + + span args() const { return args_; } + napi_value this_arg() const { return this_arg_; } + void* data() const { return data_; } + + private: + std::array inline_args_{}; + std::unique_ptr dynamic_args_{}; + span args_{}; + napi_value this_arg_{}; + void* data_{}; +}; + +} // namespace + +//============================================================================= +// NodeApiTest implementation +//============================================================================= + +std::unique_ptr CreateEnvHolder( + std::shared_ptr taskRunner, + std::function onUnhandledError); + +//============================================================================= +// NodeLiteModule implementation +//============================================================================= + +using ModuleRegisterFuncCallback = napi_value(NAPI_CDECL*)(napi_env env, + napi_value exports); +using ModuleApiVersionCallback = int32_t(NAPI_CDECL*)(); + +NodeLiteModule::NodeLiteModule(std::filesystem::path module_path) noexcept + : module_path_(std::move(module_path)) {} + +NodeLiteModule::NodeLiteModule(std::filesystem::path module_path, + InitModuleCallback init_module) noexcept + : module_path_(std::move(module_path)), + init_module_(std::move(init_module)) {} + +napi_value NodeLiteModule::LoadModule(napi_env env) { + if (state_ == State::kLoaded) { + return NodeApi::GetReferenceValue(env, exports_.get()); + } + if (state_ == State::kLoading) { + return NodeApi::GetUndefined(env); + } + NODE_LITE_ASSERT(state_ == State::kNotLoaded, + "Unexpected module '%s' state: %d", + module_path_.string().c_str(), + static_cast(state_)); + state_ = State::kLoading; + struct ResetStateIfFailed { + NodeLiteModule* module_; + ~ResetStateIfFailed() { + if (module_->state_ == State::kLoading) { + module_->state_ = State::kNotLoaded; + } + } + } reset_state_if_failed{this}; + + if (init_module_) { + napi_value exports = NodeApi::CreateObject(env); + napi_value init_exports = init_module_(env, exports); + if (init_exports != nullptr && + NodeApi::TypeOf(env, init_exports) != napi_undefined) { + exports = init_exports; + } + exports_ = MakeNodeApiRef(env, exports); + } else if (module_path_.extension() == ".js") { + exports_ = MakeNodeApiRef(env, LoadScriptModule(env)); + } else if (module_path_.extension() == ".node") { + exports_ = MakeNodeApiRef(env, LoadNativeModule(env)); + } else { + NODE_LITE_ASSERT( + false, "Unsupported module type: %s", module_path_.string().c_str()); + } + state_ = State::kLoaded; + return NodeApi::GetReferenceValue(env, exports_.get()); +} + +napi_value NodeLiteModule::LoadScriptModule(napi_env env) { + std::string module_func_wrapper = + "(function(module, exports, require, __filename, __dirname) {"; + module_func_wrapper += ReadModuleFileText(env); + + size_t source_map_index = module_func_wrapper.find("//# sourceMappingURL"); + constexpr const char* module_suffix = "\nreturn module.exports; })\n"; + if (source_map_index != std::string::npos) { + module_func_wrapper.insert(source_map_index, module_suffix); + } else { + module_func_wrapper += module_suffix; + } + + napi_value module_func = NodeApi::RunScript( + env, module_func_wrapper, module_path_.string().c_str()); + + NODE_LITE_ASSERT(NodeApi::TypeOf(env, module_func) == napi_function); + + napi_value exports = NodeApi::CreateObject(env); + napi_value file_name = NodeApi::CreateString(env, module_path_.string()); + napi_value dir_name = + NodeApi::CreateString(env, module_path_.parent_path().string()); + + napi_value module_obj = NodeApi::CreateObject(env); + NodeApi::SetProperty(env, module_obj, "exports", exports); + NodeApi::SetProperty(env, module_obj, "__filename", file_name); + NodeApi::SetProperty(env, module_obj, "__dirname", dir_name); + + napi_value require = NodeApi::CreateFunction( + env, "require", [this](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, "Expected at least one argument"); + std::string module_path = NodeApi::ToStdString(env, args[0]); + NodeLiteRuntime* runtime = NodeLiteRuntime::GetRuntime(env); + return runtime + ->ResolveModule(module_path_.parent_path().string(), module_path) + .LoadModule(env); + }); + + return NodeApi::CallFunction( + env, module_func, {module_obj, exports, require, file_name, dir_name}); +} + +napi_value NodeLiteModule::LoadNativeModule(napi_env env) { + ModuleApiVersionCallback getModuleApiVersion = + reinterpret_cast(NodeLitePlatform::LoadFunction( + env, module_path_.c_str(), "node_api_module_get_api_version_v1")); + int32_t moduleApiVersion = getModuleApiVersion ? getModuleApiVersion() : 8; + + ModuleRegisterFuncCallback moduleRegisterFunc = + reinterpret_cast( + NodeLitePlatform::LoadFunction( + env, module_path_.c_str(), "napi_register_module_v1")); + NODE_LITE_ASSERT(moduleRegisterFunc != nullptr, + "Failed to find 'napi_register_module_v1' in module: %s", + module_path_.c_str()); + + napi_value exports{}; + NODE_LITE_CALL(jsr_initialize_native_module( + env, moduleRegisterFunc, moduleApiVersion, &exports)); + return exports; +} + +std::string NodeLiteModule::ReadModuleFileText(napi_env env) { + return ReadFileText(env, module_path_); +} + +//============================================================================= +// NodeLiteRuntime implementation +//============================================================================= + +/*static*/ void NodeLiteRuntime::Run(std::vector argv) { + // Convert arguments to vector of strings and skip all options before the JS + // file name. + std::vector args; + args.reserve(argv.size()); + bool skipOptions = true; + if (argv.size() < 2) { + NodeLiteErrorHandler::ExitWithMessage("", [&](std::ostream& os) { + os << "Usage: " << argv[0] << " "; + }); + } + args.push_back(argv[0]); + for (int i = 1; i < argv.size(); i++) { + if (skipOptions && std::string_view(argv[i]).find("--") == 0) { + continue; + } + skipOptions = false; + args.push_back(argv[i]); + } + + std::shared_ptr taskRunner = + std::make_shared(); + + fs::path exe_path = fs::canonical(argv[0]); + + fs::path test_root_path = exe_path.parent_path(); + fs::path js_root = test_root_path / "test"; + if (!fs::exists(js_root)) { + test_root_path = test_root_path.parent_path(); + js_root = test_root_path / "test"; + } + if (!fs::exists(js_root)) { + NodeLiteErrorHandler::ExitWithMessage("Error: Cannot find test directory."); + } + + std::string jsFilePath = args[1]; + std::unique_ptr runtime = NodeLiteRuntime::Create( + std::move(taskRunner), + js_root.string(), + std::move(args), + NodeLiteRuntime::Callbacks{}); + runtime->RunTestScript(jsFilePath); +} + +/*static*/ std::unique_ptr NodeLiteRuntime::Create( + std::shared_ptr task_runner, + std::string js_root, + std::vector args, + Callbacks callbacks) { + std::unique_ptr runtime = + std::make_unique(PrivateTag{}, + std::move(task_runner), + std::move(js_root), + std::move(args), + std::move(callbacks)); + runtime->Initialize(); + return std::unique_ptr(runtime.release()); +} + +NodeLiteRuntime::NodeLiteRuntime( + PrivateTag, + std::shared_ptr task_runner, + std::string js_root, + std::vector args, + Callbacks callbacks) + : task_runner_(std::move(task_runner)), + js_root_(std::move(js_root)), + args_(std::move(args)), + callbacks_(std::move(callbacks)) {} + +void NodeLiteRuntime::Initialize() { + env_holder_ = + CreateEnvHolder(task_runner_, [this](napi_env env, napi_value error) { + NODE_LITE_ASSERT(env == env_, + "Unhandled error in different napi_env: %p != %p", + env, + env_); + OnUncaughtException(error); + }); + env_ = env_holder_->getEnv(); + NodeApiEnvScope env_scope{env_}; + NodeApiHandleScope handle_scope{env_}; + DefineBuiltInModules(); + DefineGlobalFunctions(); +} + +NodeLiteModule& NodeLiteRuntime::ResolveModule( + const std::string& parent_module_path, const std::string& module_path) { + napi_env env = env_; + fs::path fs_module_path = ResolveModulePath(parent_module_path, module_path); + if (auto it = registered_modules_.find(fs_module_path.string()); + it != registered_modules_.end()) { + return *it->second; + } + + if (auto [it, succeeded] = registered_modules_.try_emplace( + fs_module_path.string(), + std::make_unique(fs_module_path.string())); + succeeded) { + return *it->second; + } + + NODE_LITE_ASSERT( + false, "Failed to register module: %s", fs_module_path.string().c_str()); +} + +fs::path NodeLiteRuntime::ResolveModulePath( + const std::string& parent_module_path, const std::string& module_path) { + napi_env env = env_; + // 1. See if it is an embedded module such as "assert". + auto it = node_js_modules_.find(module_path); + if (it != node_js_modules_.end()) { + return fs::path(it->second); + } + + // 2. Check if it is a relative or an absolute path to a module. + { + fs::path fs_module_path = fs::path(module_path); + if (!fs_module_path.is_absolute()) { + fs::path fs_parent_module_path = fs::path(parent_module_path); + NODE_LITE_ASSERT(fs_parent_module_path.is_absolute(), + "Parent module path '%s' is not absolute", + parent_module_path.c_str()); + fs_module_path = fs_parent_module_path / fs_module_path; + } + fs_module_path = fs::weakly_canonical(fs_module_path); + + if (fs::exists(fs_module_path) && fs::is_regular_file(fs_module_path)) { + return fs_module_path; + } + if (fs::path result = fs::path(fs_module_path).replace_extension(".js"); + fs::exists(result)) { + return result; + } + if (fs::path result = fs_module_path / "index.js"; fs::exists(result)) { + return result; + } + // See if it is a native module. + fs::path node_module_path = + fs::path(fs_module_path).replace_extension(".node"); + if (fs::exists(node_module_path)) { + return node_module_path; + } + // See if the module was prefixed with the parent folder to disambiguate C++ + // project name. + fs::path fs_parent_folder = fs::path(parent_module_path).filename(); + node_module_path.replace_filename(fs_parent_folder.string() + "_" + + node_module_path.filename().string()); + if (fs::exists(node_module_path)) { + return node_module_path; + } + } + + // 3. Check if it is in the node_modules folder. + { + fs::path fs_module_path = fs::weakly_canonical( + fs::path(js_root_) / "node_modules" / fs::path(module_path)); + + if (fs::exists(fs_module_path) && fs::is_regular_file(fs_module_path)) { + return fs_module_path; + } + if (fs::path result = fs::path(fs_module_path).replace_extension(".js"); + fs::exists(result)) { + return result; + } + if (fs::path result = fs_module_path / "index.js"; fs::exists(result)) { + return result; + } + } + + NODE_LITE_ASSERT( + false, "Cannot resolve module path '%s'", module_path.c_str()); +} + +void NodeLiteRuntime::AddNativeModule( + const std::string& module_name, + std::function initModule) { + napi_env env = env_; + auto [_, succeeded] = registered_modules_.try_emplace( + module_name, + std::make_unique(module_name, std::move(initModule))); + NODE_LITE_ASSERT( + succeeded, "Failed to register module: %s", module_name.c_str()); +} + +void NodeLiteRuntime::RunTestScript(const std::string& script_path) { + NodeApiEnvScope env_scope{env_}; + NodeApiHandleScope handle_scope{env_}; + { + ExitOnException(env_, [this, &script_path]() { + NodeApiHandleScope scope{env_}; + NodeLiteModule& main_module = ResolveModule(js_root_, script_path); + main_module.LoadModule(env_); + }); + ExitOnException(env_, [this]() { + task_runner_->DrainTaskQueue(); + OnExit(); + on_exit_callbacks_.clear(); + on_uncaughtException_callbacks_.clear(); + }); + } +} + +void NodeLiteRuntime::OnExit() { + for (NodeApiRef& callback_ref : on_exit_callbacks_) { + napi_value callback = NodeApi::GetReferenceValue(env_, callback_ref.get()); + NodeApi::CallFunction(env_, callback, {NodeApi::CreateUInt32(env_, 0)}); + } +} + +void NodeLiteRuntime::OnUncaughtException(napi_value error) { + bool shouldExit = true; + for (NodeApiRef& callback_ref : on_uncaughtException_callbacks_) { + napi_value callback = NodeApi::GetReferenceValue(env_, callback_ref.get()); + napi_value result = NodeApi::CallFunction( + env_, + callback, + {error, NodeApi::CreateString(env_, "uncaughtException")}); + // If at least one callback returns false, we do not exit. + // TODO: (vmoroz) Investigate the Node.js behavior in that case + // if (shouldExit && NodeApi::TypeOf(env_, result) == napi_boolean) { + // shouldExit = NodeApi::GetBoolean(env_, result); + //} + shouldExit = false; + } + + if (shouldExit) { + NodeLiteErrorHandler::ExitWithJSError(env_, error); + } +} + +/*static*/ NodeLiteRuntime* NodeLiteRuntime::GetRuntime(napi_env env) { + napi_value global = NodeApi::GetGlobal(env); + return static_cast(NodeApi::GetValueExternal( + env, NodeApi::GetProperty(env, global, "__NodeLiteRuntime__"))); +} + +void NodeLiteRuntime::DefineBuiltInModules() { + napi_env env = env_; + // Define "assert" module + { + fs::path assert_path = + fs::weakly_canonical(fs::path(js_root_) / "common" / "assert.js"); + std::string assert_path_str = assert_path.string(); + NODE_LITE_ASSERT(fs::exists(assert_path), + "Failed to find assert.js file: %s", + assert_path_str.c_str()); + node_js_modules_.try_emplace(assert_path_str, assert_path_str); + node_js_modules_.try_emplace(assert_path.replace_extension().string(), + assert_path_str); + node_js_modules_.try_emplace("assert", assert_path_str); + node_js_modules_.try_emplace("node:assert", assert_path_str); + } + + // Define "child_process" module + { + node_js_modules_.try_emplace("child_process", "child_process"); + node_js_modules_.try_emplace("node:child_process", "child_process"); + AddNativeModule("child_process", [this](napi_env env, napi_value exports) { + NodeApi::SetMethod( + env_, exports, "spawnSync", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 2, + "Expected at least 2 arguments, but got: %zu", + args.size()); + std::string command = NodeApi::ToStdString(env, args[0]); + std::vector command_args = + NodeApi::ToStdStringArray(env, args[1]); + ProcessResult call_result = SpawnSync(command, command_args); + napi_value result = NodeApi::CreateObject(env); + NodeApi::SetPropertyUInt32( + env, result, "status", call_result.status); + NodeApi::SetPropertyString( + env, result, "stderr", call_result.std_error); + NodeApi::SetPropertyString( + env, result, "stdout", call_result.std_output); + NodeApi::SetPropertyNull(env, result, "signal"); + return result; + }); + return exports; + }); + } + + // Define "fs" module + { + node_js_modules_.try_emplace("fs", "fs"); + node_js_modules_.try_emplace("node:fs", "fs"); + AddNativeModule("fs", [this](napi_env env, napi_value exports) { + NodeApi::SetMethod( + env_, exports, "existsSync", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, "Expected at least 1 argument"); + fs::path path = fs::path{NodeApi::ToStdString(env, args[0])}; + return NodeApi::GetBoolean(env, fs::exists(path)); + }); + NodeApi::SetMethod( + env_, + exports, + "readFileSync", + [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, "Expected at least 1 argument"); + fs::path path = fs::path{NodeApi::ToStdString(env, args[0])}; + return NodeApi::CreateString(env, ReadFileText(env, path)); + }); + return exports; + }); + } + + // Define "path" module + { + node_js_modules_.try_emplace("path", "path"); + node_js_modules_.try_emplace("node:path", "path"); + AddNativeModule("path", [this](napi_env env, napi_value exports) { + NodeApi::SetMethod( + env_, exports, "join", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 2, + "Expected at least 2 arguments, but got: %zu", + args.size()); + fs::path path = fs::path{NodeApi::ToStdString(env, args[0])}; + for (size_t i = 1; i < args.size(); ++i) { + path /= NodeApi::ToStdString(env, args[i]); + } + return NodeApi::CreateString(env, path.string()); + }); + return exports; + }); + } +} + +void NodeLiteRuntime::DefineGlobalFunctions() { + NodeApiHandleScope scope{env_}; + napi_value global = NodeApi::GetGlobal(env_); + + // Add global.global + NodeApi::SetProperty(env_, global, "global", global); + + // Add global.__NodeLiteRuntime__ + NodeApi::SetProperty( + env_, global, "__NodeLiteRuntime__", NodeApi::CreateExternal(env_, this)); + + // Remove the global.require defined by Hermes + NodeApi::DeleteProperty(env_, global, "require"); + + // global.gc() + NodeApi::SetMethod( + env_, global, "gc", [](napi_env env, span /*args*/) { + NODE_LITE_CALL(jsr_collect_garbage(env)); + return nullptr; + }); + + auto set_immediate_cb = [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, + "Expected at least 1 argument, but got: %zu", + args.size()); + std::shared_ptr callback_ref = + std::make_shared(MakeNodeApiRef(env, args[0])); + uint32_t task_id = GetRuntime(env)->task_runner_->PostTask( + [env, callback_ref = std::move(callback_ref)]() { + ExitOnException(env, [env, &callback_ref]() { + NodeApiHandleScope scope{env}; + napi_value callback = + NodeApi::GetReferenceValue(env, callback_ref->get()); + NodeApi::CallFunction(env, callback, {}); + }); + }); + return NodeApi::CreateUInt32(env, task_id); + }; + + // global.setImmediate() + NodeApi::SetMethod(env_, global, "setImmediate", set_immediate_cb); + + // global.setTimeout() + NodeApi::SetMethod(env_, global, "setTimeout", set_immediate_cb); + + // global.clearTimeout() + NodeApi::SetMethod( + env_, global, "clearTimeout", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, + "Expected at least 1 argument, but got: %zu", + args.size()); + uint32_t task_id = NodeApi::GetValueUInt32(env, args[0]); + GetRuntime(env)->task_runner_->RemoveTask(task_id); + return nullptr; + }); + + // global.process + { + napi_value process_obj = NodeApi::CreateObject(env_); + NodeApi::SetProperty(env_, global, "process", process_obj); + + // process.argv + NodeApi::SetPropertyStringArray(env_, process_obj, "argv", args_); + + // process.execPath + NodeApi::SetPropertyString(env_, process_obj, "execPath", args_[0]); + + // process.target_config - always use "Release" to match CMAKE module output directory + NodeApi::SetPropertyString(env_, process_obj, "target_config", "Release"); + +// process.platform +#ifdef WIN32 + NodeApi::SetPropertyString(env_, process_obj, "platform", "win32"); +#else + // TODO: (vmoroz) Add support for other platforms. + NodeApi::SetPropertyString(env_, process_obj, "platform", "other"); +#endif + + // process.exit(exit_code) + NodeApi::SetMethod( + env_, process_obj, "exit", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, + "Expected at least 1 argument, but got: " + "%zu", + args.size()); + int32_t exit_code = NodeApi::GetValueInt32(env, args[0]); + exit(exit_code); + return nullptr; + }); + + // process.on('event_name', callback) + NodeApi::SetMethod( + env_, process_obj, "on", [](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 2, + "Expected at least 2 arguments, but got: %zu", + args.size()); + std::string event_name = NodeApi::ToStdString(env, args[0]); + if (event_name == "exit") { + NODE_LITE_ASSERT(NodeApi::TypeOf(env, args[1]) == napi_function, + "Expected function as second argument"); + GetRuntime(env)->on_exit_callbacks_.push_back( + MakeNodeApiRef(env, args[1])); + } else if (event_name == "uncaughtException") { + NODE_LITE_ASSERT(NodeApi::TypeOf(env, args[1]) == napi_function, + "Expected function as second argument"); + GetRuntime(env)->on_uncaughtException_callbacks_.push_back( + MakeNodeApiRef(env, args[1])); + } else { + NODE_LITE_ASSERT(false, + "Unsupported process event name: %s", + event_name.c_str()); + } + return nullptr; + }); + } + + // global.console + { + napi_value console_obj = NodeApi::CreateObject(env_); + NodeApi::SetProperty(env_, global, "console", console_obj); + + // console.log() + NodeApi::SetMethod( + env_, + console_obj, + "log", + [this](napi_env env, span args) { + NODE_LITE_ASSERT(args.size() >= 1, "Expected at least 1 argument"); + std::string message = NodeApi::ToStdString(env, args[0]); + EmitConsoleOutput(message, false); + return nullptr; + }); + + // console.error() + NodeApi::SetMethod( + env_, + console_obj, + "error", + [this](napi_env env, span args) -> napi_value { + NODE_LITE_ASSERT(args.size() >= 1, "Expected at least 1 argument"); + std::string message = NodeApi::ToStdString(env, args[0]); + EmitConsoleOutput(message, true); + return nullptr; + }); + } +} + +void NodeLiteRuntime::EmitConsoleOutput(const std::string& message, + bool is_error) { + const auto& callback = is_error ? callbacks_.stderr_callback + : callbacks_.stdout_callback; + if (callback) { + callback(message); + return; + } + + if (is_error) { + std::cerr << message << std::endl; + } else { + std::cout << message << std::endl; + } +} + +std::string NodeLiteRuntime::ProcessStack(std::string const& stack, + std::string const& assertMethod) { + // Split up the stack string into an array of stack frames + auto stackStream = std::istringstream(stack); + std::string stackFrame; + std::vector stackFrames; + while (std::getline(stackStream, stackFrame, '\n')) { + stackFrames.push_back(std::move(stackFrame)); + } + + // Remove first and last stack frames: one is the error message + // and another is the module root call. + if (!stackFrames.empty()) { + stackFrames.pop_back(); + } + if (!stackFrames.empty()) { + stackFrames.erase(stackFrames.begin()); + } + + std::string processedStack; + bool assertFuncFound = false; + std::string assertFuncPattern = assertMethod + " ("; + const std::regex locationRE("(\\w+):(\\d+)"); + std::smatch locationMatch; + // for (auto const& frame : stackFrames) { + // if (assertFuncFound) { + // std::string processedFrame; + // if (std::regex_search(frame, locationMatch, locationRE)) { + // if (auto const* scriptInfo = + // GetTestScriptInfo(locationMatch[1].str())) { + // int32_t cppLine = + // scriptInfo->line + std::stoi(locationMatch[2].str()) - 1; + // processedFrame = locationMatch.prefix().str() + + // UseSrcFilePath(scriptInfo->filePath.string()) + + // ':' + std::to_string(cppLine) + + // locationMatch.suffix().str(); + // } + // } + // processedStack += + // (!processedFrame.empty() ? processedFrame : frame) + '\n'; + // } else { + // auto pos = frame.find(assertFuncPattern); + // if (pos != std::string::npos) { + // if (frame[pos - 1] == '.' || frame[pos - 1] == ' ') { + // assertFuncFound = true; + // } + // } + // } + // } + + return processedStack; +} + +//============================================================================= +// NodeApiRefDeleter implementation +//============================================================================= + +NodeApiRefDeleter::NodeApiRefDeleter() noexcept = default; + +NodeApiRefDeleter::NodeApiRefDeleter(napi_env env) noexcept : env_(env) {} + +void NodeApiRefDeleter::operator()(napi_ref ref) noexcept { + if (ref == nullptr || env_ == nullptr) { + return; + } + napi_env env = env_; + NODE_LITE_CALL(napi_delete_reference(env, ref)); +} + +//============================================================================= +// NodeLiteTaskRunner implementation +//============================================================================= + +uint32_t NodeLiteTaskRunner::PostTask(std::function&& task) noexcept { + uint32_t task_id = next_task_id_++; + task_queue_.emplace_back(task_id, std::move(task)); + return task_id; +} + +void NodeLiteTaskRunner::RemoveTask(uint32_t task_id) noexcept { + task_queue_.remove_if( + [task_id](const std::pair>& entry) { + return entry.first == task_id; + }); +} + +void NodeLiteTaskRunner::DrainTaskQueue() noexcept { + while (!task_queue_.empty()) { + std::pair> task = + std::move(task_queue_.front()); + task_queue_.pop_front(); + task.second(); + } +} + +/*static*/ void NodeLiteTaskRunner::PostTaskCallback( + void* task_runner_data, + void* task_data, + jsr_task_run_cb task_run_cb, + jsr_data_delete_cb task_data_delete_cb, + void* deleter_data) { + NodeLiteTaskRunner* taskRunnerPtr = + static_cast*>(task_runner_data) + ->get(); + taskRunnerPtr->PostTask( + [task_run_cb, task_data, task_data_delete_cb, deleter_data]() { + if (task_run_cb != nullptr) { + task_run_cb(task_data); + } + if (task_data_delete_cb != nullptr) { + task_data_delete_cb(task_data, deleter_data); + } + }); +} + +/*static*/ void NodeLiteTaskRunner::DeleteCallback(void* data, + void* /*deleter_data*/) { + delete static_cast*>(data); +} + +//============================================================================= +// NodeApiHandleScope implementation +//============================================================================= + +NodeApiHandleScope::NodeApiHandleScope(napi_env env) noexcept : env_{env} { + NODE_LITE_CALL(napi_open_handle_scope(env, &scope_)); +} + +NodeApiHandleScope::~NodeApiHandleScope() noexcept { + napi_env env = env_; + NODE_LITE_CALL(napi_close_handle_scope(env, scope_)); +} + +//============================================================================= +// NodeApiEnvScope implementation +//============================================================================= + +NodeApiEnvScope::NodeApiEnvScope(napi_env env) noexcept : env_{env} { + NODE_LITE_CALL(jsr_open_napi_env_scope(env, &scope_)); +} + +NodeApiEnvScope ::~NodeApiEnvScope() noexcept { + if (env_ != nullptr) { + napi_env env = env_; + NODE_LITE_CALL(jsr_close_napi_env_scope(env, scope_)); + } +} + +NodeApiEnvScope::NodeApiEnvScope(NodeApiEnvScope&& other) noexcept + : env_{std::exchange(other.env_, nullptr)}, + scope_{std::exchange(other.scope_, nullptr)} {} + +NodeApiEnvScope& NodeApiEnvScope::operator=(NodeApiEnvScope&& other) noexcept { + if (this != &other) { + NodeApiEnvScope temp(std::move(*this)); + env_ = std::exchange(other.env_, nullptr); + scope_ = std::exchange(other.scope_, nullptr); + } + return *this; +} + +//============================================================================= +// NodeLiteErrorHandler implementation +//============================================================================= + +/*static*/ NodeLiteErrorHandler::Handler NodeLiteErrorHandler::SetHandler( + Handler handler) noexcept { + std::lock_guard lock{ErrorHandlerMutex()}; + Handler previous = GetHandler(); + if (handler) { + GetHandler() = std::move(handler); + } else { + GetHandler() = DefaultFatalErrorHandler; + } + return previous; +} + +/*static*/ NodeLiteErrorHandler::Handler& NodeLiteErrorHandler::GetHandler() + noexcept { + static Handler handler = DefaultFatalErrorHandler; + return handler; +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::HandleFatalError( + NodeLiteFatalErrorInfo info) { + Handler handler_copy; + { + std::lock_guard lock{ErrorHandlerMutex()}; + handler_copy = GetHandler(); + } + handler_copy(info); + std::terminate(); +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::OnNodeApiFailed( + napi_env env, napi_status error_code) { + const char* errorMessage = "An exception is pending"; + if (NodeApi::IsExceptionPending(env)) { + error_code = napi_pending_exception; + } else { + const napi_extended_error_info* error_info{}; + napi_status status = napi_get_last_error_info(env, &error_info); + if (status != napi_ok) { + NodeLiteErrorHandler::ExitWithMessage( + "", [&](std::ostream& os) { os << "Failed to get last error info: " << status; }); + } + errorMessage = error_info->error_message; + } + throw NodeLiteException(error_code, errorMessage); +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::OnAssertFailed( + napi_env env, char const* expr, char const* message) { + std::string error_message = FormatString("Assert failed: %s.", expr); + if (message != nullptr) { + std::string message_str{message}; + if (!message_str.empty()) { + error_message += " " + message_str; + } + } + napi_status error_code = NodeApi::IsExceptionPending(env) + ? napi_pending_exception + : napi_generic_failure; + + throw NodeLiteException(error_code, error_message.c_str()); +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::ExitWithJSError( + napi_env env, napi_value error) noexcept { + // TODO: protect from stack overflow + napi_valuetype error_value_type = NodeApi::TypeOf(env, error); + if (error_value_type == napi_object) { + std::string name = NodeApi::GetPropertyString(env, error, "name"); + if (name == "AssertionError") { + ExitWithJSAssertError(env, error); + } + std::string message = NodeApi::GetPropertyString(env, error, "message"); + std::string stack = NodeApi::GetPropertyString(env, error, "stack"); + ExitWithMessage("JavaScript error", [&](std::ostream& os) { + os << "Exception: " << name << '\n' + << " Message: " << message << '\n' + << "Callstack: " << '\n' + << stack; + }); + } else { + std::string message = NodeApi::CoerceToString(env, error); + ExitWithMessage("JavaScript error", + [&](std::ostream& os) { os << " Message: " << message; }); + } +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::ExitWithJSAssertError( + napi_env env, napi_value error) noexcept { + std::string message = NodeApi::GetPropertyString(env, error, "message"); + std::string method = NodeApi::GetPropertyString(env, error, "method"); + std::string expected = NodeApi::GetPropertyString(env, error, "expected"); + std::string actual = NodeApi::GetPropertyString(env, error, "actual"); + std::string source_file = + NodeApi::GetPropertyString(env, error, "sourceFile"); + int32_t source_line = NodeApi::GetPropertyInt32(env, error, "sourceLine"); + std::string error_stack = + NodeApi::GetPropertyString(env, error, "errorStack"); + if (error_stack.empty()) { + error_stack = NodeApi::GetPropertyString(env, error, "stack"); + } + std::string method_name = "assert." + method; + std::stringstream error_details; + if (method_name != "assert.fail") { + error_details << " Expected: " << expected << '\n' + << " Actual: " << actual << '\n'; + } + + ExitWithMessage("JavaScript assertion error", [&](std::ostream& os) { + os << "Exception: " + << "AssertionError" << '\n' + << " Method: " << method_name << '\n' + << " Message: " << message << '\n' + << error_details.str(/*a filler for formatting*/) + << "Callstack: " << '\n' + << error_stack; + }); +} + +/*static*/ [[noreturn]] void NodeLiteErrorHandler::ExitWithMessage( + const std::string& message, + std::function get_error_details, + int exit_code) noexcept { + std::ostringstream details_stream; + if (get_error_details) { + get_error_details(details_stream); + } + std::string details = details_stream.str(); + + HandleFatalError(NodeLiteFatalErrorInfo{ + .message = message, + .details = details, + .exit_code = exit_code, + }); +} + +//============================================================================= +// NodeApi implementation +//============================================================================= + +/*static*/ bool NodeApi::IsExceptionPending(napi_env env) { + bool result{}; + NODE_LITE_CALL(napi_is_exception_pending(env, &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetAndClearLastException(napi_env env) { + napi_value result{}; + NODE_LITE_CALL(napi_get_and_clear_last_exception(env, &result)); + return result; +} + +/*static*/ void NodeApi::ThrowError(napi_env env, napi_value error) { + NODE_LITE_CALL(napi_throw(env, error)); +} + +/*static*/ void NodeApi::ThrowError(napi_env env, const char* error_message) { + NODE_LITE_CALL(napi_throw_error(env, "", error_message)); +} + +/*static*/ napi_value NodeApi::GetNull(napi_env env) { + napi_value result{}; + NODE_LITE_CALL(napi_get_null(env, &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetUndefined(napi_env env) { + napi_value result{}; + NODE_LITE_CALL(napi_get_undefined(env, &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetGlobal(napi_env env) { + napi_value result{}; + NODE_LITE_CALL(napi_get_global(env, &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetBoolean(napi_env env, bool value) { + napi_value result{}; + NODE_LITE_CALL(napi_get_boolean(env, value, &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetReferenceValue(napi_env env, napi_ref ref) { + napi_value result{}; + NODE_LITE_CALL(napi_get_reference_value(env, ref, &result)); + return result; +} + +/*static*/ napi_value NodeApi::CreateUInt32(napi_env env, std::uint32_t value) { + napi_value result{}; + NODE_LITE_CALL(napi_create_uint32(env, value, &result)); + return result; +} + +/*static*/ napi_value NodeApi::CreateString(napi_env env, + std::string_view value) { + napi_value result{}; + NODE_LITE_CALL( + napi_create_string_utf8(env, value.data(), value.size(), &result)); + return result; +} + +/*static*/ napi_value NodeApi::CreateStringArray( + napi_env env, std::vector const& value) { + napi_value result{}; + NODE_LITE_CALL(napi_create_array(env, &result)); + + uint32_t index = 0; + for (const std::string& item : value) { + NODE_LITE_CALL( + napi_set_element(env, result, index++, CreateString(env, item))); + } + return result; +} + +/*static*/ napi_value NodeApi::CreateObject(napi_env env) { + napi_value result{}; + NODE_LITE_CALL(napi_create_object(env, &result)); + return result; +} + +/*static*/ napi_value NodeApi::CreateExternal(napi_env env, void* data) { + napi_value result{}; + NODE_LITE_CALL(napi_create_external(env, data, nullptr, nullptr, &result)); + return result; +} + +/*static*/ int32_t NodeApi::GetValueInt32(napi_env env, napi_value value) { + int32_t result{}; + NODE_LITE_CALL(napi_get_value_int32(env, value, &result)); + return result; +} + +/*static*/ uint32_t NodeApi::GetValueUInt32(napi_env env, napi_value value) { + uint32_t result{}; + NODE_LITE_CALL(napi_get_value_uint32(env, value, &result)); + return result; +} + +/*static*/ void* NodeApi::GetValueExternal(napi_env env, napi_value value) { + void* result{}; + NODE_LITE_CALL(napi_get_value_external(env, value, &result)); + return result; +} + +/*static*/ bool NodeApi::HasProperty(napi_env env, + napi_value obj, + std::string_view utf8_name) { + bool result{}; + NODE_LITE_CALL(napi_has_named_property(env, obj, utf8_name.data(), &result)); + return result; +} + +/*static*/ napi_value NodeApi::GetProperty(napi_env env, + napi_value obj, + std::string_view utf8_name) { + napi_value result{}; + NODE_LITE_CALL(napi_get_named_property(env, obj, utf8_name.data(), &result)); + return result; +} + +/*static*/ std::string NodeApi::GetPropertyString(napi_env env, + napi_value obj, + std::string_view utf8_name) { + if (HasProperty(env, obj, utf8_name)) { + return ToStdString(env, GetProperty(env, obj, utf8_name)); + } else { + return ""; + } +} + +/*static*/ int32_t NodeApi::GetPropertyInt32(napi_env env, + napi_value obj, + std::string_view utf8_name) { + return GetValueInt32(env, GetProperty(env, obj, utf8_name)); +} + +/*static*/ std::string NodeApi::CoerceToString(napi_env env, napi_value value) { + napi_value str_value; + NODE_LITE_CALL(napi_coerce_to_string(env, value, &str_value)); + return ToStdString(env, str_value); +} + +/*static*/ void NodeApi::SetProperty(napi_env env, + napi_value obj, + std::string_view utf8_name, + napi_value value) { + NODE_LITE_CALL(napi_set_named_property(env, obj, utf8_name.data(), value)); +} + +/*static*/ void NodeApi::SetPropertyUInt32(napi_env env, + napi_value obj, + std::string_view utf8_name, + uint32_t value) { + SetProperty(env, obj, utf8_name, CreateUInt32(env, value)); +} + +/*static*/ void NodeApi::SetPropertyString(napi_env env, + napi_value obj, + std::string_view utf8_name, + std::string_view value) { + SetProperty(env, obj, utf8_name, CreateString(env, value)); +} + +/*static*/ void NodeApi::SetPropertyStringArray( + napi_env env, + napi_value obj, + std::string_view utf8_name, + std::vector const& value) { + SetProperty(env, obj, utf8_name, CreateStringArray(env, value)); +} + +/*static*/ void NodeApi::SetPropertyNull(napi_env env, + napi_value obj, + std::string_view utf8_name) { + SetProperty(env, obj, utf8_name, GetNull(env)); +} + +/*static*/ void NodeApi::SetMethod(napi_env env, + napi_value obj, + std::string_view utf8_name, + NodeApiCallback cb) { + NodeApi::SetProperty(env, obj, utf8_name, CreateFunction(env, utf8_name, cb)); +} + +/*static*/ bool NodeApi::DeleteProperty(napi_env env, + napi_value obj, + std::string_view utf8_name) { + bool result{}; + NODE_LITE_CALL( + napi_delete_property(env, obj, CreateString(env, utf8_name), &result)); + return result; +} + +/*static*/ std::string NodeApi::ToStdString(napi_env env, napi_value value) { + size_t str_size{}; + NODE_LITE_CALL(napi_get_value_string_utf8(env, value, nullptr, 0, &str_size)); + std::string result(str_size, '\0'); + NODE_LITE_CALL(napi_get_value_string_utf8( + env, value, &result[0], str_size + 1, nullptr)); + return result; +} + +/*static*/ std::vector NodeApi::ToStdStringArray( + napi_env env, napi_value value) { + std::vector result; + bool is_array; + NODE_LITE_CALL(napi_is_array(env, value, &is_array)); + if (is_array) { + uint32_t length; + NODE_LITE_CALL(napi_get_array_length(env, value, &length)); + result.reserve(length); + for (uint32_t i = 0; i < length; i++) { + napi_value element; + NODE_LITE_CALL(napi_get_element(env, value, i, &element)); + result.push_back(CoerceToString(env, element)); + } + } + return result; +} + +/*static*/ napi_value NodeApi::RunScript(napi_env env, napi_value script) { + napi_value result{}; + NODE_LITE_CALL(napi_run_script(env, script, nullptr, &result)); + return result; +} + +/*static*/ napi_value NodeApi::RunScript(napi_env env, + const std::string& code, + char const* source_url) { + napi_value script = NodeApi::CreateString(env, code); + + if (source_url != nullptr) { + napi_value result{}; + NODE_LITE_CALL(jsr_run_script(env, script, source_url, &result)); + return result; + } + return RunScript(env, script); +} + +/*static*/ napi_valuetype NodeApi::TypeOf(napi_env env, napi_value value) { + napi_valuetype result{}; + NODE_LITE_CALL(napi_typeof(env, value, &result)); + return result; +} + +/*static*/ napi_value NodeApi::CallFunction(napi_env env, + napi_value func, + span args) { + napi_value result{}; + NODE_LITE_CALL(napi_call_function( + env, GetUndefined(env), func, args.size(), args.data(), &result)); + return result; +} + +/*static*/ napi_value NodeApi::CreateFunction(napi_env env, + std::string_view name, + NodeApiCallback cb) { + napi_value result{}; + NODE_LITE_CALL(napi_create_function( + env, + name.data(), + name.size(), + [](napi_env env, napi_callback_info info) { + napi_value result{}; + ThrowJSErrorOnException(env, [env, info, &result]() { + NodeApiCallbackInfo callback_info{env, info}; + NodeApiCallback* cb = + static_cast(callback_info.data()); + result = (*cb)(env, callback_info.args()); + }); + return result; + }, + // TODO: (vmoroz) Find a way to delete it on close. + new NodeApiCallback(std::move(cb)), + &result)); + return result; +} + +ProcessResult RunNodeLiteScript(const std::filesystem::path& js_root, + const std::filesystem::path& script_path, + NodeLiteRuntime::Callbacks callbacks) { + ProcessResult result{}; + std::ostringstream stdout_stream; + std::ostringstream stderr_stream; + + NodeLiteRuntime::Callbacks effective_callbacks; + auto stdout_cb = callbacks.stdout_callback; + auto stderr_cb = callbacks.stderr_callback; + effective_callbacks.stdout_callback = + [stdout_cb, &stdout_stream](const std::string& message) { + if (stdout_cb) { + stdout_cb(message); + } + stdout_stream << message << '\n'; + }; + effective_callbacks.stderr_callback = + [stderr_cb, &stderr_stream](const std::string& message) { + if (stderr_cb) { + stderr_cb(message); + } + stderr_stream << message << '\n'; + }; + + auto fatal_handler = [&result](const NodeLiteFatalErrorInfo& info) { + result.status = info.exit_code; + if (!info.message.empty()) { + result.std_error = info.message; + } + if (!info.details.empty()) { + if (!result.std_error.empty()) { + result.std_error += '\n'; + } + result.std_error += info.details; + } + throw NodeLiteFatalError(info); + }; + + NodeLiteErrorHandler::Handler previous_handler = + NodeLiteErrorHandler::SetHandler(fatal_handler); + + try { + auto task_runner = std::make_shared(); + std::vector args{"node_lite", script_path.string()}; + auto runtime = NodeLiteRuntime::Create(std::move(task_runner), + js_root.string(), + std::move(args), + std::move(effective_callbacks)); + runtime->RunTestScript(script_path.string()); + result.status = 0; + } catch (const NodeLiteFatalError&) { + // Fatal error captured in result + } catch (const std::exception& e) { + NodeLiteErrorHandler::SetHandler(previous_handler); + result.status = -1; + result.std_error = e.what(); + return result; + } catch (...) { + NodeLiteErrorHandler::SetHandler(previous_handler); + result.status = -1; + result.std_error = "Unknown error"; + return result; + } + + NodeLiteErrorHandler::SetHandler(previous_handler); + + result.std_output = stdout_stream.str(); + if (!result.std_output.empty() && result.std_output.back() == '\n') { + result.std_output.pop_back(); + } + + std::string stderr_logs = stderr_stream.str(); + if (!stderr_logs.empty() && stderr_logs.back() == '\n') { + stderr_logs.pop_back(); + } + if (!stderr_logs.empty()) { + if (!result.std_error.empty()) { + result.std_error += '\n'; + } + result.std_error += stderr_logs; + } + + return result; +} + +} // namespace node_api_tests + +int main(int argc, char* argv[]) { + node_api_tests::NodeLiteRuntime::Run( + std::vector(argv, argv + argc)); +} diff --git a/Tests/NodeApi/node_lite.h b/Tests/NodeApi/node_lite.h new file mode 100644 index 00000000..e8bba7e2 --- /dev/null +++ b/Tests/NodeApi/node_lite.h @@ -0,0 +1,406 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// A simple Node.js-like runtime that runs Node-API test scripts. + +#ifndef NODE_API_TEST_NODE_LITE_H +#define NODE_API_TEST_NODE_LITE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "child_process.h" +#include "compat.h" +#include "string_utils.h" + +#define NAPI_EXPERIMENTAL +#include "js_runtime_api.h" + +#define NODE_LITE_CALL(expr) \ + do { \ + napi_status temp_status__ = (expr); \ + if (temp_status__ != napi_status::napi_ok) { \ + NodeLiteErrorHandler::OnNodeApiFailed(env, temp_status__); \ + } \ + } while (false) + +#define NODE_LITE_ASSERT(expr, ...) \ + do { \ + if (!(expr)) { \ + NodeLiteErrorHandler::OnAssertFailed( \ + env, #expr, FormatString("" __VA_ARGS__).c_str()); \ + } \ + } while (false) + +namespace node_api_tests { + +// Forward declarations +class NodeLiteModule; +class NodeLiteRuntime; +class NodeLiteTaskRunner; +class NodeApiRefDeleter; +class NodeApiHandleScope; +class NodeApiEnvScope; +class NodeLiteErrorHandler; + +struct IEnvHolder { + virtual ~IEnvHolder() {} + virtual napi_env getEnv() = 0; +}; + +class NodeLiteTaskRunner { + public: + using QueueEntry = std::pair>; + + uint32_t PostTask(std::function&& task) noexcept; + void RemoveTask(uint32_t task_id) noexcept; + void DrainTaskQueue() noexcept; + + static void PostTaskCallback(void* task_runner_data, + void* task_data, + jsr_task_run_cb task_run_cb, + jsr_data_delete_cb task_data_delete_cb, + void* deleter_data); + + static void DeleteCallback(void* data, void* /*deleter_data*/); + + private: + std::list task_queue_; + uint32_t next_task_id_{1}; +}; + +class NodeLiteException : public std::runtime_error { + public: + explicit NodeLiteException(napi_status error_status, + const char* message) noexcept + : runtime_error{message}, error_status_{error_status} {} + + napi_status error_status() const noexcept { return error_status_; } + + private: + napi_status error_status_; +}; + +struct NodeLiteFatalErrorInfo { + std::string message; + std::string details; + int exit_code{1}; +}; + +class NodeLiteFatalError : public std::runtime_error { + public: + explicit NodeLiteFatalError(NodeLiteFatalErrorInfo info) + : std::runtime_error{info.message.c_str()}, info_{std::move(info)} {} + + const NodeLiteFatalErrorInfo& info() const noexcept { return info_; } + + private: + NodeLiteFatalErrorInfo info_; +}; + +class NodeLiteErrorHandler { + public: + using Handler = std::function; + + static Handler SetHandler(Handler handler) noexcept; + + [[noreturn]] static void OnNodeApiFailed(napi_env env, + napi_status error_status); + + [[noreturn]] static void OnAssertFailed(napi_env env, + char const* expr, + char const* message); + + [[noreturn]] static void ExitWithJSError(napi_env env, + napi_value error) noexcept; + + [[noreturn]] static void ExitWithJSAssertError(napi_env env, + napi_value error) noexcept; + + [[noreturn]] static void ExitWithMessage( + const std::string& message, + std::function get_error_details = nullptr, + int exit_code = 1) noexcept; + + private: + static Handler& GetHandler() noexcept; + [[noreturn]] static void HandleFatalError(NodeLiteFatalErrorInfo info); +}; + +// Define NodeApiRef "smart pointer" for napi_ref as unique_ptr with a custom +// deleter. +class NodeApiRefDeleter { + public: + NodeApiRefDeleter() noexcept; + explicit NodeApiRefDeleter(napi_env env) noexcept; + + void operator()(napi_ref ref) noexcept; + + private: + napi_env env_{}; +}; + +using NodeApiRef = std::unique_ptr; + +class NodeApiHandleScope { + public: + explicit NodeApiHandleScope(napi_env env) noexcept; + ~NodeApiHandleScope() noexcept; + + private: + napi_env env_{}; + napi_handle_scope scope_{}; +}; + +class NodeApiEnvScope { + public: + explicit NodeApiEnvScope(napi_env env) noexcept; + + ~NodeApiEnvScope() noexcept; + + NodeApiEnvScope(NodeApiEnvScope&& other) noexcept; + NodeApiEnvScope& operator=(NodeApiEnvScope&& other) noexcept; + + NodeApiEnvScope(const NodeApiEnvScope&) = delete; + NodeApiEnvScope& operator=(const NodeApiEnvScope&) = delete; + + private: + napi_env env_{}; + jsr_napi_env_scope scope_{}; +}; + +class NodeLiteModule { + public: + using InitModuleCallback = + std::function; + + explicit NodeLiteModule(std::filesystem::path module_path) noexcept; + explicit NodeLiteModule(std::filesystem::path module_path, + InitModuleCallback init_module) noexcept; + + napi_value LoadModule(napi_env env); + + NodeLiteModule(const NodeLiteModule&) = delete; + NodeLiteModule& operator=(const NodeLiteModule&) = delete; + + private: + napi_value LoadScriptModule(napi_env env); + napi_value LoadNativeModule(napi_env env); + std::string ReadModuleFileText(napi_env env); + + private: + enum class State { + kNotLoaded, + kLoading, + kLoaded, + }; + + private: + State state_{State::kNotLoaded}; + std::filesystem::path module_path_; + InitModuleCallback init_module_; + NodeApiRef exports_; +}; + +// The Node.js-like runtime that is enough to run Node-API tests. +class NodeLiteRuntime { + struct PrivateTag {}; + + public: + struct Callbacks { + std::function stdout_callback{}; + std::function stderr_callback{}; + }; + + static std::unique_ptr Create( + std::shared_ptr task_runner, + std::string js_root, + std::vector args, + Callbacks callbacks); + + explicit NodeLiteRuntime(PrivateTag tag, + std::shared_ptr task_runner, + std::string js_root, + std::vector args, + Callbacks callbacks); + + static void Run(std::vector args); + + NodeLiteModule& ResolveModule(const std::string& parent_module_path, + const std::string& module_path); + + std::filesystem::path ResolveModulePath(const std::string& parent_module_path, + const std::string& module_path); + + void RunTestScript(const std::string& script_path); + + void AddNativeModule( + const std::string& module_name, + std::function initModule); + + void HandleUnhandledPromiseRejections(); + void OnExit(); + void OnUncaughtException(napi_value error); + + std::string ProcessStack(std::string const& stack, + std::string const& assertMethod); + + static NodeLiteRuntime* GetRuntime(napi_env env); + + private: + void Initialize(); + void DefineGlobalFunctions(); + void DefineBuiltInModules(); + void EmitConsoleOutput(const std::string& message, bool is_error); + + private: + std::shared_ptr task_runner_; + std::string js_root_; + std::vector args_; + Callbacks callbacks_{}; + std::unique_ptr env_holder_; + napi_env env_{}; + std::unordered_map> + registered_modules_; + std::unordered_map node_js_modules_; + std::vector on_exit_callbacks_; + std::vector on_uncaughtException_callbacks_; +}; + +class NodeLitePlatform { + public: + static void* LoadFunction(napi_env env, + const std::filesystem::path& lib_path, + const std::string& function_name) noexcept; +}; + +using NodeApiCallback = + std::function args)>; + +// Wraps up Node-API function calls. +// To simplify usage patterns it throws NodeApiException on errors. +class NodeApi { + public: + static bool IsExceptionPending(napi_env env); + + static napi_value GetAndClearLastException(napi_env env); + + static void ThrowError(napi_env env, napi_value error); + + static void ThrowError(napi_env env, const char* error_message); + + static napi_value GetNull(napi_env env); + + static napi_value GetUndefined(napi_env env); + + static napi_value GetGlobal(napi_env env); + + static napi_value GetBoolean(napi_env env, bool value); + + static napi_value GetReferenceValue(napi_env env, napi_ref ref); + + static napi_value CreateUInt32(napi_env env, std::uint32_t value); + + static napi_value CreateString(napi_env env, std::string_view value); + + static napi_value CreateStringArray(napi_env env, + std::vector const& value); + + static napi_value CreateObject(napi_env env); + + static napi_value CreateExternal(napi_env env, void* data); + + static int32_t GetValueInt32(napi_env env, napi_value value); + + static uint32_t GetValueUInt32(napi_env env, napi_value value); + + static void* GetValueExternal(napi_env env, napi_value value); + + static bool HasProperty(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static napi_value GetProperty(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static std::string GetPropertyString(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static int32_t GetPropertyInt32(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static void SetProperty(napi_env env, + napi_value obj, + std::string_view utf8_name, + napi_value value); + + static void SetPropertyUInt32(napi_env env, + napi_value obj, + std::string_view utf8_name, + uint32_t value); + + static void SetPropertyString(napi_env env, + napi_value obj, + std::string_view utf8_name, + std::string_view value); + + static void SetPropertyStringArray(napi_env env, + napi_value obj, + std::string_view utf8_name, + std::vector const& value); + + static void SetPropertyNull(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static void SetMethod(napi_env env, + napi_value obj, + std::string_view utf8_name, + NodeApiCallback cb); + + static bool DeleteProperty(napi_env env, + napi_value obj, + std::string_view utf8_name); + + static std::string CoerceToString(napi_env env, napi_value value); + + static std::string ToStdString(napi_env env, napi_value value); + + static std::vector ToStdStringArray(napi_env env, + napi_value value); + + static napi_value RunScript(napi_env env, napi_value script); + + static napi_value RunScript(napi_env env, + const std::string& code, + char const* source_url); + + static napi_valuetype TypeOf(napi_env env, napi_value value); + + static napi_value CallFunction(napi_env env, + napi_value func, + span args); + + static napi_value CreateFunction(napi_env env, + std::string_view name, + NodeApiCallback cb); +}; + +ProcessResult RunNodeLiteScript( + const std::filesystem::path& js_root, + const std::filesystem::path& script_path, + NodeLiteRuntime::Callbacks callbacks = NodeLiteRuntime::Callbacks{}); + +} // namespace node_api_tests + +#endif // !NODE_API_TEST_NODE_LITE_H diff --git a/Tests/NodeApi/node_lite_android.cpp b/Tests/NodeApi/node_lite_android.cpp new file mode 100644 index 00000000..5476aeec --- /dev/null +++ b/Tests/NodeApi/node_lite_android.cpp @@ -0,0 +1,22 @@ +#include "node_lite.h" + +#include + +namespace node_api_tests { + +/*static*/ void* NodeLitePlatform::LoadFunction( + napi_env /*env*/, + const std::filesystem::path& lib_path, + const std::string& function_name) noexcept +{ + void* handle = dlopen(lib_path.string().c_str(), RTLD_NOW | RTLD_LOCAL); + if (handle == nullptr) + { + return nullptr; + } + + void* symbol = dlsym(handle, function_name.c_str()); + return symbol; +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/node_lite_jsruntimehost.cpp b/Tests/NodeApi/node_lite_jsruntimehost.cpp new file mode 100644 index 00000000..1c524f53 --- /dev/null +++ b/Tests/NodeApi/node_lite_jsruntimehost.cpp @@ -0,0 +1,159 @@ +#include "node_lite.h" + +#include +#include + +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#include "js_native_api_javascriptcore.h" +#elif defined(__ANDROID__) +#include +#include "js_native_api_v8.h" +#include +#endif + +namespace node_api_tests { + +namespace { + +class JsRuntimeHostEnvHolder : public IEnvHolder { + public: + JsRuntimeHostEnvHolder( + std::shared_ptr /*taskRunner*/, + std::function onUnhandledError) + : onUnhandledError_(std::move(onUnhandledError)) { +#if defined(__APPLE__) + context_ = JSGlobalContextCreateInGroup(nullptr, nullptr); + env_ = Napi::Attach(context_); +#elif defined(__ANDROID__) + V8Platform::EnsureInitialized(); + + allocator_.reset(v8::ArrayBuffer::Allocator::NewDefaultAllocator()); + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = allocator_.get(); + isolate_ = v8::Isolate::New(create_params); + + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + v8::Local context = v8::Context::New(isolate_); + context_.Reset(isolate_, context); + v8::Context::Scope context_scope(context); + env_ = Napi::Attach(context); +#else + (void)onUnhandledError_; + throw std::runtime_error( + "node_lite is only implemented for Apple platforms in this port."); +#endif + } + + ~JsRuntimeHostEnvHolder() override { +#if defined(__APPLE__) + if (env_ != nullptr) { + Napi::Env napiEnv{env_}; + + if (onUnhandledError_) { + bool hasPending = false; + if (napi_is_exception_pending(env_, &hasPending) == napi_ok && + hasPending) { + napi_value error{}; + if (napi_get_and_clear_last_exception(env_, &error) == napi_ok) { + onUnhandledError_(env_, error); + } + } + } + + if (context_ != nullptr) { + JSGlobalContextRelease(context_); + context_ = nullptr; + } + + Napi::Detach(napiEnv); + env_ = nullptr; + } else if (context_ != nullptr) { + JSGlobalContextRelease(context_); + context_ = nullptr; + } +#elif defined(__ANDROID__) + if (env_ != nullptr && isolate_ != nullptr) { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope context_scope(context); + + if (onUnhandledError_) { + bool hasPending = false; + if (napi_is_exception_pending(env_, &hasPending) == napi_ok && hasPending) { + napi_value error{}; + if (napi_get_and_clear_last_exception(env_, &error) == napi_ok) { + onUnhandledError_(env_, error); + } + } + } + + Napi::Env napiEnv{env_}; + Napi::Detach(napiEnv); + env_ = nullptr; + } + + context_.Reset(); + + if (isolate_ != nullptr) { + isolate_->Dispose(); + isolate_ = nullptr; + } + + allocator_.reset(); +#endif + } + + napi_env getEnv() override { return env_; } + + private: +#if defined(__APPLE__) + JSGlobalContextRef context_{}; +#elif defined(__ANDROID__) + class V8Platform { + public: + static void EnsureInitialized() { + std::call_once(init_flag_, []() { + platform_ = v8::platform::NewDefaultPlatform(); + v8::V8::InitializePlatform(platform_.get()); + v8::V8::Initialize(); + }); + } + + private: + static std::once_flag init_flag_; + static std::unique_ptr platform_; + }; + + v8::Isolate* isolate_{nullptr}; + v8::Global context_; + std::unique_ptr allocator_{}; +#endif + napi_env env_{}; + std::function onUnhandledError_{}; +}; + +#if defined(__ANDROID__) +std::once_flag JsRuntimeHostEnvHolder::V8Platform::init_flag_{}; +std::unique_ptr JsRuntimeHostEnvHolder::V8Platform::platform_{}; +#endif + +} // namespace + +std::unique_ptr CreateEnvHolder( + std::shared_ptr taskRunner, + std::function onUnhandledError) { + return std::make_unique( + std::move(taskRunner), std::move(onUnhandledError)); +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/node_lite_posix.cpp b/Tests/NodeApi/node_lite_posix.cpp new file mode 100644 index 00000000..c2968514 --- /dev/null +++ b/Tests/NodeApi/node_lite_posix.cpp @@ -0,0 +1,43 @@ +#include +#if defined(__ANDROID__) +#include +#endif +#include "node_lite.h" + +namespace node_api_tests { + +/*static*/ void* NodeLitePlatform::LoadFunction( + napi_env env, + const std::filesystem::path& lib_path, + const std::string& function_name) noexcept { +#if defined(__ANDROID__) && (__ANDROID_API__ < 29) + void* library_handle = dlopen(lib_path.string().c_str(), RTLD_NOW | RTLD_LOCAL); + if (library_handle == nullptr) { + return nullptr; + } + + return dlsym(library_handle, function_name.c_str()); +#else + void* library_handle = dlopen(lib_path.string().c_str(), RTLD_NOW | RTLD_LOCAL); + if (library_handle == nullptr) { + const char* error_message = dlerror(); + NODE_LITE_ASSERT(false, + "Failed to load dynamic library: %s. Error: %s", + lib_path.c_str(), + error_message != nullptr ? error_message : "Unknown error"); + return nullptr; + } + + dlerror(); // Clear any existing error state before dlsym. + void* symbol = dlsym(library_handle, function_name.c_str()); + const char* error_message = dlerror(); + NODE_LITE_ASSERT(error_message == nullptr, + "Failed to resolve symbol: %s in %s. Error: %s", + function_name.c_str(), + lib_path.c_str(), + error_message != nullptr ? error_message : "Unknown error"); + return symbol; +#endif +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/node_lite_windows.cpp b/Tests/NodeApi/node_lite_windows.cpp new file mode 100644 index 00000000..4af34561 --- /dev/null +++ b/Tests/NodeApi/node_lite_windows.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include "node_lite.h" + +namespace node_api_tests { + +//============================================================================= +// NodeLitePlatform implementation +//============================================================================= + +/*static*/ void* NodeLitePlatform::LoadFunction( + napi_env env, + const std::filesystem::path& lib_path, + const std::string& function_name) noexcept { + HMODULE dll_module = ::LoadLibraryA(lib_path.string().c_str()); + NODE_LITE_ASSERT(dll_module != NULL, + "Failed to load DLL: %s. Error: %s", + lib_path.c_str(), + std::strerror(errno)); + return ::GetProcAddress(dll_module, function_name.c_str()); +} +} // namespace node_api_tests diff --git a/Tests/NodeApi/string_utils.cpp b/Tests/NodeApi/string_utils.cpp new file mode 100644 index 00000000..583f3e15 --- /dev/null +++ b/Tests/NodeApi/string_utils.cpp @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "string_utils.h" +#include + +namespace node_api_tests { + +std::string FormatString(const char *format, ...) noexcept { + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); + std::string result = + std::string(std::vsnprintf(nullptr, 0, format, args1), '\0'); + va_end(args1); + std::vsnprintf(&result[0], result.size() + 1, format, args2); + va_end(args2); + return result; +} + +std::string ReplaceAll( + std::string str, + std::string_view from, + std::string_view to) noexcept { + std::string result = std::move(str); + if (from.empty()) + return result; + size_t start_pos = 0; + while ((start_pos = result.find(from, start_pos)) != std::string::npos) { + result.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case if 'to' contains 'from', like + // replacing 'x' with 'yx' + } + return result; +} + +} // namespace node_api_tests \ No newline at end of file diff --git a/Tests/NodeApi/string_utils.h b/Tests/NodeApi/string_utils.h new file mode 100644 index 00000000..0d290208 --- /dev/null +++ b/Tests/NodeApi/string_utils.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef NODE_API_TEST_STRING_UTILS_H +#define NODE_API_TEST_STRING_UTILS_H + +#include +#include + +namespace node_api_tests { + +std::string FormatString(const char *format, ...) noexcept; + +std::string ReplaceAll( + std::string str, + std::string_view from, + std::string_view to) noexcept; + +} // namespace node_api_tests + +#endif // !NODE_API_TEST_STRING_UTILS_H \ No newline at end of file diff --git a/Tests/NodeApi/test/.clang-format b/Tests/NodeApi/test/.clang-format new file mode 100644 index 00000000..b3fd9613 --- /dev/null +++ b/Tests/NodeApi/test/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never diff --git a/Tests/NodeApi/test/CMakeLists.txt b/Tests/NodeApi/test/CMakeLists.txt new file mode 100644 index 00000000..01521826 --- /dev/null +++ b/Tests/NodeApi/test/CMakeLists.txt @@ -0,0 +1,91 @@ +if(WIN32) + # "npx" interrupts current shell script execution without the "call" + set(npx cmd /c npx) + set(yarn cmd /c yarn) +else() + set(npx "npx") + set(yarn "yarn") +endif() + +# copy JS Tools package files +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/babel.config.js + ${CMAKE_CURRENT_BINARY_DIR}/package.json + ${CMAKE_CURRENT_BINARY_DIR}/yarn.lock + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/babel.config.js + ${CMAKE_CURRENT_SOURCE_DIR}/package.json + ${CMAKE_CURRENT_SOURCE_DIR}/yarn.lock + ${CMAKE_CURRENT_BINARY_DIR} +) +add_custom_target(copyNodeApiJSToolsFiles) + +# run "yarn install" +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/node_modules.sha1 + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/package.json + ${CMAKE_CURRENT_BINARY_DIR}/yarn.lock + COMMAND ${yarn} install --frozen-lockfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) +add_custom_target(installNodeApiTestJsTools + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/node_modules.sha1 +) +add_dependencies(installNodeApiTestJsTools copyNodeApiJSToolsFiles) + +# add the Babel transform commands for each test JS file +set(testJSRootDir ${CMAKE_CURRENT_SOURCE_DIR}) + +# Collect all .js files recursively +file(GLOB_RECURSE basicsTestJSFiles "basics/*.js") +file(GLOB_RECURSE commonTestJSFiles "common/*.js") +file(GLOB_RECURSE jsNativeApiTestJSFiles "js-native-api/*.js") +set(testJSFiles + ${basicsTestJSFiles} + ${commonTestJSFiles} + ${jsNativeApiTestJSFiles}) + +foreach(absoluteTestJSFile ${testJSFiles}) + # create target directory + file(RELATIVE_PATH testJSFile ${testJSRootDir} ${absoluteTestJSFile}) + get_filename_component(testJSDir ${testJSFile} DIRECTORY) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${testJSDir}) + + # generate Hermes-compatible JavaScript code + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/${testJSFile} + ${CMAKE_CURRENT_BINARY_DIR}/${testJSFile}.map + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${testJSFile} + COMMAND + ${npx} + "babel" + "--retain-lines" + "--source-maps" + "true" + "--out-file" + "${CMAKE_CURRENT_BINARY_DIR}/${testJSFile}" + "--source-map-target" + "${CMAKE_CURRENT_BINARY_DIR}/${testJSFile}.map" + "${CMAKE_CURRENT_SOURCE_DIR}/${testJSFile}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + # build a list of all outputs + list(APPEND transformedJSFiles + ${CMAKE_CURRENT_BINARY_DIR}/${testJSFile} + ${CMAKE_CURRENT_BINARY_DIR}/${testJSFile}.map + ) +endforeach() + +# run the Babel transforms for all required output files +add_custom_target(transformJSFiles + DEPENDS ${transformedJSFiles} +) +add_dependencies(transformJSFiles installNodeApiTestJsTools) + +if(JSR_NODE_API_BUILD_NATIVE_TESTS) + add_subdirectory(js-native-api) +endif() diff --git a/Tests/NodeApi/test/babel.config.js b/Tests/NodeApi/test/babel.config.js new file mode 100644 index 00000000..c8cda4fe --- /dev/null +++ b/Tests/NodeApi/test/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ['module:@react-native/babel-preset', + { "unstable_transformProfile": "hermes-canary"}] + ], +}; \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/async_rejected.js b/Tests/NodeApi/test/basics/async_rejected.js new file mode 100644 index 00000000..3c2d956a --- /dev/null +++ b/Tests/NodeApi/test/basics/async_rejected.js @@ -0,0 +1,19 @@ +function resolveAfterTimeout() { + return new Promise((_, reject) => { + setTimeout(() => { + reject("test async rejected"); + }, 0); + }); +} + +async function asyncCall() { + console.log("test async calling"); + try { + const result = await resolveAfterTimeout(); + console.log(`Unexpected: ${result}`); + } catch (error) { + console.error(`Expected: ${error}`); + } +} + +asyncCall(); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/async_resolved.js b/Tests/NodeApi/test/basics/async_resolved.js new file mode 100644 index 00000000..d9b44145 --- /dev/null +++ b/Tests/NodeApi/test/basics/async_resolved.js @@ -0,0 +1,15 @@ +function resolveAfterTimeout() { + return new Promise((resolve) => { + setTimeout(() => { + resolve("test async resolved"); + }, 0); + }); +} + +async function asyncCall() { + console.log("test async calling"); + const result = await resolveAfterTimeout(); + console.log(`Expected: ${result}`); +} + +asyncCall(); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/hello.js b/Tests/NodeApi/test/basics/hello.js new file mode 100644 index 00000000..7a2bb74e --- /dev/null +++ b/Tests/NodeApi/test/basics/hello.js @@ -0,0 +1 @@ +console.log("Hello"); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/mustcall_failure.js b/Tests/NodeApi/test/basics/mustcall_failure.js new file mode 100644 index 00000000..105650f8 --- /dev/null +++ b/Tests/NodeApi/test/basics/mustcall_failure.js @@ -0,0 +1,3 @@ +const common = require('../common'); + +common.mustCall(); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/mustcall_success.js b/Tests/NodeApi/test/basics/mustcall_success.js new file mode 100644 index 00000000..cec9ac6a --- /dev/null +++ b/Tests/NodeApi/test/basics/mustcall_success.js @@ -0,0 +1,4 @@ +const common = require('../common'); + +var fn = common.mustCall(); +fn(); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/mustnotcall_failure.js b/Tests/NodeApi/test/basics/mustnotcall_failure.js new file mode 100644 index 00000000..49db06cc --- /dev/null +++ b/Tests/NodeApi/test/basics/mustnotcall_failure.js @@ -0,0 +1,4 @@ +const common = require('../common'); + +var fn = common.mustNotCall(); +fn(); \ No newline at end of file diff --git a/Tests/NodeApi/test/basics/mustnotcall_success.js b/Tests/NodeApi/test/basics/mustnotcall_success.js new file mode 100644 index 00000000..b933a30f --- /dev/null +++ b/Tests/NodeApi/test/basics/mustnotcall_success.js @@ -0,0 +1,3 @@ +const common = require('../common'); + +common.mustNotCall(); diff --git a/Tests/NodeApi/test/basics/throw_string.js b/Tests/NodeApi/test/basics/throw_string.js new file mode 100644 index 00000000..f6cc7b60 --- /dev/null +++ b/Tests/NodeApi/test/basics/throw_string.js @@ -0,0 +1 @@ +throw "Script failed"; \ No newline at end of file diff --git a/Tests/NodeApi/test/common/assert.js b/Tests/NodeApi/test/common/assert.js new file mode 100644 index 00000000..12bb4dac --- /dev/null +++ b/Tests/NodeApi/test/common/assert.js @@ -0,0 +1,400 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// The JavaScript code in this file is adopted from the Node.js project. +// See the src\napi\Readme.md about the Node.js copyright notice. + +"use strict"; + +class AssertionError extends Error { + constructor(options) { + const { message, actual, expected, method, errorStack } = options; + + super(String(message)); + + this.name = "AssertionError"; + this.method = String(method); + this.actual = String(actual); + this.expected = String(expected); + this.errorStack = errorStack || ""; + setAssertionSource(this, method); + } +} + +function setAssertionSource(error, method) { + let result = { sourceFile: "", sourceLine: 0 }; + const stackArray = (error.errorStack || error.stack).split("\n"); + const methodNamePattern = `${method} (`; + let methodNameFound = false; + for (const stackFrame of stackArray) { + if (methodNameFound) { + const stackFrameParts = stackFrame.split(":"); + if (stackFrameParts.length >= 2) { + let sourceFile = stackFrameParts[0]; + if (sourceFile.startsWith(" at ")) { + sourceFile = sourceFile.substr(7); + } + result = { sourceFile, sourceLine: Number(stackFrameParts[1]) }; + } + break; + } else { + methodNameFound = stackFrame.indexOf(methodNamePattern) >= 0; + } + } + Object.assign(error, result); +} + +const assert = (module.exports = ok); + +assert.fail = function fail(message) { + message = message || "Failed"; + let errorInfo = message; + if (typeof message !== "object") { + errorInfo = { message, method: fail.name }; + } + throw new AssertionError(errorInfo); +}; + +function innerOk(fn, argLen, value, message) { + if (!value) { + if (argLen === 0) { + message = "No value argument passed to `assert.ok()`"; + } else if (message == null) { + message = "The expression evaluated to a falsy value"; + } + + assert.fail({ + message, + actual: formatValue(value), + expected: formatValue(true), + method: fn.name, + }); + } +} + +// Pure assertion tests whether a value is truthy, as determined by !!value. +function ok(...args) { + innerOk(ok, args.length, ...args); +} +assert.ok = ok; + +let compareErrorMessage = undefined; +function innerComparison( + method, + compare, + defaultMessage, + argLen, + actual, + expected, + message +) { + if (!compare(actual, expected)) { + if (argLen < 2) { + message = `'assert.${method.name}' expects two or more arguments.`; + } else if (message == null) { + message = defaultMessage; + } + if (typeof compareErrorMessage === "string") { + message += "; " + compareErrorMessage; + compareErrorMessage = undefined; + } + assert.fail({ + message, + actual: formatValue(actual), + expected: formatValue(expected), + method: method.name, + }); + } +} + +assert.strictEqual = function strictEqual(...args) { + innerComparison( + strictEqual, + Object.is, + "Values are not strict equal", + args.length, + ...args + ); +}; + +assert.notStrictEqual = function notStrictEqual(...args) { + innerComparison( + notStrictEqual, + negate(Object.is), + "Values must not be strict equal", + args.length, + ...args + ); +}; + +assert.deepStrictEqual = function deepStrictEqual(...args) { + innerComparison( + deepStrictEqual, + isDeepStrictEqual, + "Values are not deep strict equal", + args.length, + ...args + ); +}; + +assert.notDeepStrictEqual = function notDeepStrictEqual(...args) { + innerComparison( + notDeepStrictEqual, + negate(isDeepStrictEqual), + "Values must not be deep strict equal", + args.length, + ...args + ); +}; + +function innerThrows(method, argLen, fn, expected, message) { + let actual = "Did not throw"; + function succeeds() { + try { + fn(); + return false; + } catch (error) { + if (typeof expected === "function") { + if (expected.prototype !== undefined && error instanceof expected) { + return true; + } else { + return expected(error); + } + } else if (expected instanceof RegExp) { + actual = `${error.name}: ${error.message}`; + return expected.test(actual); + } else if (expected) { + actual = `${error.name}: ${error.message}`; + if (expected.name && expected.name != error.name) { + return false; + } else if (expected.message && expected.message != error.message) { + return false; + } else if (expected.code && expected.code != error.code) { + return false; + } + } + return true; + } + } + + if (argLen < 1 || typeof fn !== "function") { + message = `'assert.${method.name}' expects a function parameter.`; + } else if (message == null) { + if (expected) { + message = `'assert.${method.name}' failed to throw an exception that matches '${expected}'.`; + } else { + message = `'assert.${method.name}' failed to throw an exception.`; + } + } + + if (!succeeds()) { + throw new AssertionError({ + message, + actual, + expected, + method: method.name, + }); + } +} + +assert.throws = function throws(...args) { + innerThrows(throws, args.length, ...args); +}; + +function innerMatch(method, argLen, value, expected, message) { + let succeeds = false; + if (argLen < 1 || typeof value !== "string") { + message = `'assert.${method.name}' expects a string parameter.`; + } else if (!(expected instanceof RegExp)) { + message = `'assert.${method.name}' expects a RegExp as a second parameter.`; + } else { + succeeds = expected.test(value); + if (!succeeds && message == null) { + message = `'assert.${method.name}' failed to match '${expected}'.`; + } + } + + if (!succeeds) { + throw new AssertionError({ + message, + actual: value, + expected, + method: method.name, + }); + } +} + +assert.match = function match(...args) { + innerMatch(match, args.length, ...args); +}; + +function negate(compare) { + return (...args) => !compare(...args); +} + +function isDeepStrictEqual(left, right) { + function check(left, right) { + if (left === right) { + return true; + } + if (typeof left !== typeof right) { + compareErrorMessage = `Different types: ${typeof left} vs ${typeof right}`; + return false; + } + if (Array.isArray(left)) { + return Array.isArray(right) && checkArray(left, right); + } + if (typeof left === "number") { + return isNaN(left) && isNaN(right); + } + if (typeof left === "object") { + return typeof right === "object" && checkObject(left, right); + } + return false; + } + + function checkArray(left, right) { + if (left.length !== right.length) { + compareErrorMessage = `Different array lengths: ${left.length} vs ${right.length}`; + return false; + } + for (let i = 0; i < left.length; ++i) { + if (!check(left[i], right[i])) { + compareErrorMessage = `Different values at index ${i}: ${left[i]} vs ${right[i]}`; + return false; + } + } + return true; + } + + function checkObject(left, right) { + const leftNames = Object.getOwnPropertyNames(left); + const rightNames = Object.getOwnPropertyNames(right); + if (leftNames.length !== rightNames.length) { + compareErrorMessage = `Different set of property names: ${leftNames.length} vs ${rightNames.length}`; + return false; + } + for (let i = 0; i < leftNames.length; ++i) { + if (!check(left[leftNames[i]], right[leftNames[i]])) { + compareErrorMessage = `Different values for property '${leftNames[i]}': ${left[leftNames[i]]} vs ${right[leftNames[i]]}`; + return false; + } + } + const leftSymbols = Object.getOwnPropertySymbols(left); + const rightSymbols = Object.getOwnPropertySymbols(right); + if (leftSymbols.length !== rightSymbols.length) { + compareErrorMessage = `Different set of symbol names: ${leftSymbols.length} vs ${rightSymbols.length}`; + return false; + } + for (let i = 0; i < leftSymbols.length; ++i) { + if (!check(left[leftSymbols[i]], right[leftSymbols[i]])) { + compareErrorMessage = `${leftSymbols[i].toString()}: different value`; + return false; + } + } + return check(Object.getPrototypeOf(left), Object.getPrototypeOf(right)); + } + + return check(left, right); +} + +const mustCallChecks = []; + +function runCallChecks() { + const failed = mustCallChecks.filter((context) => { + if ("minimum" in context) { + context.messageSegment = `at least ${context.minimum}`; + return context.actual < context.minimum; + } + context.messageSegment = `exactly ${context.exact}`; + return context.actual !== context.exact; + }); + + mustCallChecks.length = 0; + + failed.forEach((context) => { + assert.fail({ + message: `Mismatched ${context.name} function calls`, + actual: `${context.actual} calls`, + expected: `${context.messageSegment} calls`, + method: context.method.name, + errorStack: context.stack, + }); + }); +}; +assert.runCallChecks = runCallChecks; + +function getCallSite() { + try { + throw new Error(""); + } catch (err) { + return err.stack; + } +} + +assert.mustNotCall = function mustNotCall(msg) { + return function mustNotCall(...args) { + assert.fail({ + message: String(msg || "Function should not have been called"), + actual: + args.length > 0 + ? `Called with arguments: ${args.map(String).join(", ")}` + : "Called without arguments", + expected: "Not to be called", + method: mustNotCall.name, + }); + }; +}; + +assert.mustCall = function mustCall(fn, exact) { + return _mustCallInner(fn, exact, "exact", mustCall); +}; + +assert.mustCallAtLeast = function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, "minimum", mustCallAtLeast); +}; + +const noop = () => {}; + +function _mustCallInner(fn, criteria = 1, field, method) { + if (typeof fn === "number") { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== "number") { + throw new TypeError(`Invalid ${field} value: ${criteria}`); + } + + const context = { + [field]: criteria, + actual: 0, + stack: getCallSite(), + name: fn.name || "", + method, + }; + + // Add the exit listener only once to avoid listener leak warnings + if (mustCallChecks.length === 0) process.on('exit', runCallChecks); + + mustCallChecks.push(context); + + return function () { + context.actual++; + return fn.apply(this, arguments); + }; +} + +function formatValue(value) { + let type = typeof value; + if (type === "object") { + if (Array.isArray(value)) { + return " []"; + } else { + return " {}"; + } + } + return `<${type}> ${value}`; +} diff --git a/Tests/NodeApi/test/common/gc.js b/Tests/NodeApi/test/common/gc.js new file mode 100644 index 00000000..66f77055 --- /dev/null +++ b/Tests/NodeApi/test/common/gc.js @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// The JavaScript code in this file is adopted from the Node.js project. +// See the src\napi\Readme.md about the Node.js copyright notice. +"use strict"; + +function gcUntil(name, condition) { + if (typeof name === "function") { + condition = name; + name = undefined; + } + return new Promise((resolve, reject) => { + let count = 0; + function gcAndCheck() { + setImmediate(() => { + count++; + global.gc(); + if (condition()) { + resolve(); + } else if (count < 10) { + gcAndCheck(); + } else { + reject(name === undefined ? undefined : "Test " + name + " failed"); + } + }); + } + gcAndCheck(); + }); +} + +Object.assign(module.exports, { + gcUntil, +}); diff --git a/Tests/NodeApi/test/common/index.js b/Tests/NodeApi/test/common/index.js new file mode 100644 index 00000000..e4ec6151 --- /dev/null +++ b/Tests/NodeApi/test/common/index.js @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// The JavaScript code in this file is adopted from the Node.js project. +// See the src\napi\Readme.md about the Node.js copyright notice. +"use strict"; + +const { mustCall, mustCallAtLeast, mustNotCall } = require("assert"); +const { gcUntil } = require("gc"); + +const buildType = process.target_config; +const isWindows = process.platform === 'win32'; + +// Returns true if the exit code "exitCode" and/or signal name "signal" +// represent the exit code and/or signal name of a node process that aborted, +// false otherwise. +function nodeProcessAborted(exitCode, signal) { + // Depending on the compiler used, node will exit with either + // exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT). + let expectedExitCodes = [132, 133, 134]; + + // On platforms using KSH as the default shell (like SmartOS), + // when a process aborts, KSH exits with an exit code that is + // greater than 256, and thus the exit code emitted with the 'exit' + // event is null and the signal is set to either SIGILL, SIGTRAP, + // or SIGABRT (depending on the compiler). + const expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT']; + + // On Windows, 'aborts' are of 2 types, depending on the context: + // (i) Exception breakpoint, if --abort-on-uncaught-exception is on + // which corresponds to exit code 2147483651 (0x80000003) + // (ii) Otherwise, _exit(134) which is called in place of abort() due to + // raising SIGABRT exiting with ambiguous exit code '3' by default + if (isWindows) + expectedExitCodes = [0x80000003, 134]; + + // When using --abort-on-uncaught-exception, V8 will use + // base::OS::Abort to terminate the process. + // Depending on the compiler used, the shell or other aspects of + // the platform used to build the node binary, this will actually + // make V8 exit by aborting or by raising a signal. In any case, + // one of them (exit code or signal) needs to be set to one of + // the expected exit codes or signals. + if (signal !== null) { + return expectedSignals.includes(signal); + } + return expectedExitCodes.includes(exitCode); +} + +Object.assign(module.exports, { + buildType, + gcUntil, + mustCall, + mustCallAtLeast, + mustNotCall, + nodeProcessAborted, +}); \ No newline at end of file diff --git a/Tests/NodeApi/test/js-native-api/.gitignore b/Tests/NodeApi/test/js-native-api/.gitignore new file mode 100644 index 00000000..6e29bde8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/.gitignore @@ -0,0 +1,7 @@ +.buildstamp +.docbuildstamp +Makefile +*.Makefile +*.mk +gyp-mac-tool +/*/build diff --git a/Tests/NodeApi/test/js-native-api/2_function_arguments/2_function_arguments.c b/Tests/NodeApi/test/js-native-api/2_function_arguments/2_function_arguments.c new file mode 100644 index 00000000..c03085db --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/2_function_arguments/2_function_arguments.c @@ -0,0 +1,39 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype0 == napi_number && valuetype1 == napi_number, + "Wrong argument type. Numbers expected."); + + double value0; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value0)); + + double value1; + NODE_API_CALL(env, napi_get_value_double(env, args[1], &value1)); + + napi_value sum; + NODE_API_CALL(env, napi_create_double(env, value0 + value1, &sum)); + + return sum; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("add", Add); + NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc)); + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/2_function_arguments/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/2_function_arguments/CMakeLists.txt new file mode 100644 index 00000000..258ed16e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/2_function_arguments/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(2_function_arguments + SOURCES + 2_function_arguments.c +) diff --git a/Tests/NodeApi/test/js-native-api/2_function_arguments/binding.gyp b/Tests/NodeApi/test/js-native-api/2_function_arguments/binding.gyp new file mode 100644 index 00000000..8f89a61e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/2_function_arguments/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "2_function_arguments", + "sources": [ + "2_function_arguments.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/2_function_arguments/test.js b/Tests/NodeApi/test/js-native-api/2_function_arguments/test.js new file mode 100644 index 00000000..2966cc0b --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/2_function_arguments/test.js @@ -0,0 +1,6 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/2_function_arguments`); + +assert.strictEqual(addon.add(3, 5), 8); diff --git a/Tests/NodeApi/test/js-native-api/3_callbacks/3_callbacks.c b/Tests/NodeApi/test/js-native-api/3_callbacks/3_callbacks.c new file mode 100644 index 00000000..fd7b6618 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/3_callbacks/3_callbacks.c @@ -0,0 +1,58 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value RunCallback(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, + "Wrong number of arguments. Expects a single argument."); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_function, + "Wrong type of arguments. Expects a function as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NODE_API_ASSERT(env, valuetype1 == napi_undefined, + "Additional arguments should be undefined."); + + napi_value argv[1]; + const char* str = "hello world"; + size_t str_len = strlen(str); + NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, argv)); + + napi_value global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value cb = args[0]; + NODE_API_CALL(env, napi_call_function(env, global, cb, 1, argv, NULL)); + + return NULL; +} + +static napi_value RunCallbackWithRecv(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value cb = args[0]; + napi_value recv = args[1]; + NODE_API_CALL(env, napi_call_function(env, recv, cb, 0, NULL, NULL)); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[2] = { + DECLARE_NODE_API_PROPERTY("RunCallback", RunCallback), + DECLARE_NODE_API_PROPERTY("RunCallbackWithRecv", RunCallbackWithRecv), + }; + NODE_API_CALL(env, napi_define_properties(env, exports, 2, desc)); + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/3_callbacks/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/3_callbacks/CMakeLists.txt new file mode 100644 index 00000000..02134621 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/3_callbacks/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(3_callbacks + SOURCES + 3_callbacks.c +) diff --git a/Tests/NodeApi/test/js-native-api/3_callbacks/binding.gyp b/Tests/NodeApi/test/js-native-api/3_callbacks/binding.gyp new file mode 100644 index 00000000..d64b5e48 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/3_callbacks/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "3_callbacks", + "sources": [ + "3_callbacks.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/3_callbacks/test.js b/Tests/NodeApi/test/js-native-api/3_callbacks/test.js new file mode 100644 index 00000000..ace0f2a7 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/3_callbacks/test.js @@ -0,0 +1,22 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/3_callbacks`); + +addon.RunCallback(function(msg) { + assert.strictEqual(msg, 'hello world'); +}); + +function testRecv(desiredRecv) { + addon.RunCallbackWithRecv(function() { + assert.strictEqual(this, desiredRecv); + }, desiredRecv); +} + +testRecv(undefined); +testRecv(null); +testRecv(5); +testRecv(true); +testRecv('Hello'); +testRecv([]); +testRecv({}); diff --git a/Tests/NodeApi/test/js-native-api/4_object_factory/4_object_factory.c b/Tests/NodeApi/test/js-native-api/4_object_factory/4_object_factory.c new file mode 100644 index 00000000..38169b0f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/4_object_factory/4_object_factory.c @@ -0,0 +1,24 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value obj; + NODE_API_CALL(env, napi_create_object(env, &obj)); + + NODE_API_CALL(env, napi_set_named_property(env, obj, "msg", args[0])); + + return obj; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, + napi_create_function(env, "exports", -1, CreateObject, NULL, &exports)); + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/4_object_factory/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/4_object_factory/CMakeLists.txt new file mode 100644 index 00000000..b4a9917c --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/4_object_factory/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(4_object_factory + SOURCES + 4_object_factory.c +) diff --git a/Tests/NodeApi/test/js-native-api/4_object_factory/binding.gyp b/Tests/NodeApi/test/js-native-api/4_object_factory/binding.gyp new file mode 100644 index 00000000..86f8b1f0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/4_object_factory/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "4_object_factory", + "sources": [ + "4_object_factory.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/4_object_factory/test.js b/Tests/NodeApi/test/js-native-api/4_object_factory/test.js new file mode 100644 index 00000000..fbfbd67f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/4_object_factory/test.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/4_object_factory`); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +assert.strictEqual(`${obj1.msg} ${obj2.msg}`, 'hello world'); diff --git a/Tests/NodeApi/test/js-native-api/5_function_factory/5_function_factory.c b/Tests/NodeApi/test/js-native-api/5_function_factory/5_function_factory.c new file mode 100644 index 00000000..744a8c72 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/5_function_factory/5_function_factory.c @@ -0,0 +1,24 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value MyFunction(napi_env env, napi_callback_info info) { + napi_value str; + NODE_API_CALL(env, napi_create_string_utf8(env, "hello world", -1, &str)); + return str; +} + +static napi_value CreateFunction(napi_env env, napi_callback_info info) { + napi_value fn; + NODE_API_CALL(env, + napi_create_function(env, "theFunction", -1, MyFunction, NULL, &fn)); + return fn; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, + napi_create_function(env, "exports", -1, CreateFunction, NULL, &exports)); + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/5_function_factory/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/5_function_factory/CMakeLists.txt new file mode 100644 index 00000000..4a7e51a7 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/5_function_factory/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(5_function_factory + SOURCES + 5_function_factory.c +) diff --git a/Tests/NodeApi/test/js-native-api/5_function_factory/binding.gyp b/Tests/NodeApi/test/js-native-api/5_function_factory/binding.gyp new file mode 100644 index 00000000..06bd385e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/5_function_factory/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "5_function_factory", + "sources": [ + "5_function_factory.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/5_function_factory/test.js b/Tests/NodeApi/test/js-native-api/5_function_factory/test.js new file mode 100644 index 00000000..bacb22ce --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/5_function_factory/test.js @@ -0,0 +1,7 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/5_function_factory`); + +const fn = addon(); +assert.strictEqual(fn(), 'hello world'); // 'hello world' diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/6_object_wrap/CMakeLists.txt new file mode 100644 index 00000000..27486730 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/CMakeLists.txt @@ -0,0 +1,21 @@ +add_node_api_module(myobject + SOURCES + myobject.cc + myobject.h +) + +add_node_api_module(myobject_basic_finalizer + SOURCES + myobject.cc + myobject.h + DEFINES + NAPI_EXPERIMENTAL +) + +add_node_api_module(nested_wrap + SOURCES + nested_wrap.cc + nested_wrap.h + DEFINES + "NAPI_VERSION=10" +) diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/binding.gyp b/Tests/NodeApi/test/js-native-api/6_object_wrap/binding.gyp new file mode 100644 index 00000000..e7a9d7ba --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/binding.gyp @@ -0,0 +1,28 @@ +{ + "targets": [ + { + "target_name": "myobject", + "sources": [ + "myobject.cc", + "myobject.h", + ] + }, + { + "target_name": "myobject_basic_finalizer", + "defines": [ "NAPI_EXPERIMENTAL" ], + "sources": [ + "myobject.cc", + "myobject.h", + ] + }, + { + "target_name": "nested_wrap", + # Test without basic finalizers as it schedules differently. + "defines": [ "NAPI_VERSION=10" ], + "sources": [ + "nested_wrap.cc", + "nested_wrap.h", + ], + }, + ] +} diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.cc b/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.cc new file mode 100644 index 00000000..5633d929 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.cc @@ -0,0 +1,270 @@ +#include "myobject.h" +#include "../common.h" +#include "../entry_point.h" +#include "assert.h" + +typedef int32_t FinalizerData; + +napi_ref MyObject::constructor; + +MyObject::MyObject(double value) + : value_(value), env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor(node_api_basic_env env, + void* nativeObject, + void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; + + FinalizerData* data; + NODE_API_BASIC_CALL_RETURN_VOID( + env, napi_get_instance_data(env, reinterpret_cast(&data))); + *data += 1; +} + +void MyObject::Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + {"value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0}, + {"valueReadonly", + nullptr, + nullptr, + GetValue, + nullptr, + 0, + napi_default, + 0}, + DECLARE_NODE_API_PROPERTY("plusOne", PlusOne), + DECLARE_NODE_API_PROPERTY("multiply", Multiply), + }; + + napi_value cons; + NODE_API_CALL_RETURN_VOID( + env, + napi_define_class(env, + "MyObject", + -1, + New, + nullptr, + sizeof(properties) / sizeof(napi_property_descriptor), + properties, + &cons)); + + NODE_API_CALL_RETURN_VOID(env, + napi_create_reference(env, cons, 1, &constructor)); + + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "MyObject", cons)); +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_value new_target; + NODE_API_CALL(env, napi_get_new_target(env, info, &new_target)); + bool is_constructor = (new_target != nullptr); + + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + if (is_constructor) { + // Invoked as constructor: `new MyObject(...)` + double value = 0; + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype != napi_undefined) { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value)); + } + + MyObject* obj = new MyObject(value); + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap(env, + _this, + obj, + MyObject::Destructor, + nullptr /* finalize_hint */, + &obj->wrapper_)); + + return _this; + } + + // Invoked as plain function `MyObject(...)`, turn into construct call. + argc = 1; + napi_value argv[1] = {args[0]}; + + napi_value cons; + NODE_API_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + napi_value instance; + NODE_API_CALL(env, napi_new_instance(env, cons, argc, argv, &instance)); + + return instance; +} + +napi_value MyObject::GetValue(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value num; + NODE_API_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + NODE_API_CALL(env, napi_get_value_double(env, args[0], &obj->value_)); + + return nullptr; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->value_ += 1; + + napi_value num; + NODE_API_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::Multiply(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + double multiple = 1; + if (argc >= 1) { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &multiple)); + } + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value cons; + NODE_API_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + const int kArgCount = 1; + napi_value argv[kArgCount]; + NODE_API_CALL(env, napi_create_double(env, obj->value_ * multiple, argv)); + + napi_value instance; + NODE_API_CALL(env, napi_new_instance(env, cons, kArgCount, argv, &instance)); + + return instance; +} + +// This finalizer should never be invoked. +void ObjectWrapDanglingReferenceFinalizer(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) { + assert(0 && "unreachable"); +} + +napi_ref dangling_ref; +napi_value ObjectWrapDanglingReference(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + // Create a napi_wrap and remove it immediately, whilst leaving the out-param + // ref dangling (not deleted). + NODE_API_CALL(env, + napi_wrap(env, + args[0], + nullptr, + ObjectWrapDanglingReferenceFinalizer, + nullptr, + &dangling_ref)); + NODE_API_CALL(env, napi_remove_wrap(env, args[0], nullptr)); + + return args[0]; +} + +napi_value ObjectWrapDanglingReferenceTest(napi_env env, + napi_callback_info info) { + napi_value out; + napi_value ret; + NODE_API_CALL(env, napi_get_reference_value(env, dangling_ref, &out)); + + if (out == nullptr) { + // If the napi_ref has been invalidated, delete it. + NODE_API_CALL(env, napi_delete_reference(env, dangling_ref)); + NODE_API_CALL(env, napi_get_boolean(env, true, &ret)); + } else { + // The dangling napi_ref is still valid. + NODE_API_CALL(env, napi_get_boolean(env, false, &ret)); + } + return ret; +} + +static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + FinalizerData* data; + napi_value result; + + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr)); + NODE_API_CALL(env, + napi_get_instance_data(env, reinterpret_cast(&data))); + NODE_API_CALL(env, napi_create_int32(env, *data, &result)); + return result; +} + +static void finalizeData(napi_env env, void* data, void* hint) { + delete reinterpret_cast(data); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + FinalizerData* data = new FinalizerData; + *data = 0; + NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, nullptr)); + + MyObject::Init(env, exports); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("objectWrapDanglingReference", + ObjectWrapDanglingReference), + DECLARE_NODE_API_PROPERTY("objectWrapDanglingReferenceTest", + ObjectWrapDanglingReferenceTest), + DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.h b/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.h new file mode 100644 index 00000000..fcb2e575 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/myobject.h @@ -0,0 +1,28 @@ +#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(node_api_basic_env env, + void* nativeObject, + void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static napi_value New(napi_env env, napi_callback_info info); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + static napi_value Multiply(napi_env env, napi_callback_info info); + static napi_ref constructor; + double value_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.cc b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.cc new file mode 100644 index 00000000..1c8594c8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.cc @@ -0,0 +1,99 @@ +#include "nested_wrap.h" +#include "../common.h" +#include "../entry_point.h" + +napi_ref NestedWrap::constructor{}; +static int finalization_count = 0; + +NestedWrap::NestedWrap() {} + +NestedWrap::~NestedWrap() { + napi_delete_reference(env_, wrapper_); + + // Delete the nested reference as well. + napi_delete_reference(env_, nested_); +} + +void NestedWrap::Destructor(node_api_basic_env env, + void* nativeObject, + void* /*finalize_hint*/) { + // Once this destructor is called, it cancels all pending + // finalizers for the object by deleting the references. + NestedWrap* obj = static_cast(nativeObject); + delete obj; + + finalization_count++; +} + +void NestedWrap::Init(napi_env env, napi_value exports) { + napi_value cons; + NODE_API_CALL_RETURN_VOID( + env, + napi_define_class( + env, "NestedWrap", -1, New, nullptr, 0, nullptr, &cons)); + + NODE_API_CALL_RETURN_VOID(env, + napi_create_reference(env, cons, 1, &constructor)); + + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "NestedWrap", cons)); +} + +napi_value NestedWrap::New(napi_env env, napi_callback_info info) { + napi_value new_target; + NODE_API_CALL(env, napi_get_new_target(env, info, &new_target)); + bool is_constructor = (new_target != nullptr); + NODE_API_BASIC_ASSERT_BASE( + is_constructor, "Constructor called without new", nullptr); + + napi_value this_val; + NODE_API_CALL(env, + napi_get_cb_info(env, info, 0, nullptr, &this_val, nullptr)); + + NestedWrap* obj = new NestedWrap(); + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap(env, + this_val, + obj, + NestedWrap::Destructor, + nullptr /* finalize_hint */, + &obj->wrapper_)); + + // Create a second napi_ref to be deleted in the destructor. + NODE_API_CALL(env, + napi_add_finalizer(env, + this_val, + obj, + NestedWrap::Destructor, + nullptr /* finalize_hint */, + &obj->nested_)); + + return this_val; +} + +static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalization_count, &result)); + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NestedWrap::Init(env, exports); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.h b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.h new file mode 100644 index 00000000..584f24de --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.h @@ -0,0 +1,33 @@ +#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ +#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ + +#include + +/** + * Test that an napi_ref can be nested inside another ObjectWrap. + * + * This test shows a critical case where a finalizer deletes an napi_ref + * whose finalizer is also scheduled. + */ + +class NestedWrap { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(node_api_basic_env env, + void* nativeObject, + void* finalize_hint); + + private: + explicit NestedWrap(); + ~NestedWrap(); + + static napi_value New(napi_env env, napi_callback_info info); + + static napi_ref constructor; + + napi_env env_{}; + napi_ref wrapper_{}; + napi_ref nested_{}; +}; + +#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.js b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.js new file mode 100644 index 00000000..726c6931 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/nested_wrap.js @@ -0,0 +1,20 @@ +// Flags: --expose-gc + +'use strict'; +const common = require('../../common'); +const { gcUntil } = require('../../common/gc'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/nested_wrap`); + +// This test verifies that ObjectWrap and napi_ref can be nested and finalized +// correctly with a non-basic finalizer. +(() => { + let obj = new addon.NestedWrap(); + obj = null; + // Silent eslint about unused variables. + assert.strictEqual(obj, null); +})(); + +gcUntil('object-wrap-ref', () => { + return addon.getFinalizerCallCount() === 1; +}); diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/test-basic-finalizer.js b/Tests/NodeApi/test/js-native-api/6_object_wrap/test-basic-finalizer.js new file mode 100644 index 00000000..5a7ccff4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/test-basic-finalizer.js @@ -0,0 +1,24 @@ +// Flags: --expose-gc + +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/myobject_basic_finalizer`); + +// This test verifies that ObjectWrap can be correctly finalized with a node_api_basic_finalizer +// in the current JS loop tick +(() => { + let obj = new addon.MyObject(9); + obj = null; + // Silent eslint about unused variables. + assert.strictEqual(obj, null); +})(); + +for (let i = 0; i < 10; ++i) { + global.gc(); + if (addon.getFinalizerCallCount() === 1) { + break; + } +} + +assert.strictEqual(addon.getFinalizerCallCount(), 1); diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/test-object-wrap-ref.js b/Tests/NodeApi/test/js-native-api/6_object_wrap/test-object-wrap-ref.js new file mode 100644 index 00000000..8f236410 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/test-object-wrap-ref.js @@ -0,0 +1,14 @@ +// Flags: --expose-gc + +'use strict'; +const common = require('../../common'); +const addon = require(`./build/${common.buildType}/myobject`); +const { gcUntil } = require('../../common/gc'); + +(function scope() { + addon.objectWrapDanglingReference({}); +})(); + +gcUntil('object-wrap-ref', () => { + return addon.objectWrapDanglingReferenceTest(); +}); diff --git a/Tests/NodeApi/test/js-native-api/6_object_wrap/test.js b/Tests/NodeApi/test/js-native-api/6_object_wrap/test.js new file mode 100644 index 00000000..809fddf2 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/6_object_wrap/test.js @@ -0,0 +1,48 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/myobject`); + +const getterOnlyErrorRE = + /^TypeError: Cannot (set|assign to) property .*( of #<.*>)? which has only a getter$/; + +const valueDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'value'); +const valueReadonlyDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'valueReadonly'); +const plusOneDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'plusOne'); +assert.strictEqual(typeof valueDescriptor.get, 'function'); +assert.strictEqual(typeof valueDescriptor.set, 'function'); +assert.strictEqual(valueDescriptor.value, undefined); +assert.strictEqual(valueDescriptor.enumerable, false); +assert.strictEqual(valueDescriptor.configurable, false); +assert.strictEqual(typeof valueReadonlyDescriptor.get, 'function'); +assert.strictEqual(valueReadonlyDescriptor.set, undefined); +assert.strictEqual(valueReadonlyDescriptor.value, undefined); +assert.strictEqual(valueReadonlyDescriptor.enumerable, false); +assert.strictEqual(valueReadonlyDescriptor.configurable, false); + +assert.strictEqual(plusOneDescriptor.get, undefined); +assert.strictEqual(plusOneDescriptor.set, undefined); +assert.strictEqual(typeof plusOneDescriptor.value, 'function'); +assert.strictEqual(plusOneDescriptor.enumerable, false); +assert.strictEqual(plusOneDescriptor.configurable, false); + +const obj = new addon.MyObject(9); +assert.strictEqual(obj.value, 9); +obj.value = 10; +assert.strictEqual(obj.value, 10); +assert.strictEqual(obj.valueReadonly, 10); +assert.throws(() => { obj.valueReadonly = 14; }, getterOnlyErrorRE); +assert.strictEqual(obj.plusOne(), 11); +assert.strictEqual(obj.plusOne(), 12); +assert.strictEqual(obj.plusOne(), 13); + +assert.strictEqual(obj.multiply().value, 13); +assert.strictEqual(obj.multiply(10).value, 130); + +const newobj = obj.multiply(-1); +assert.strictEqual(newobj.value, -13); +assert.strictEqual(newobj.valueReadonly, -13); +assert.notStrictEqual(obj, newobj); diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/7_factory_wrap.cc b/Tests/NodeApi/test/js-native-api/7_factory_wrap/7_factory_wrap.cc new file mode 100644 index 00000000..b0ff0063 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/7_factory_wrap.cc @@ -0,0 +1,32 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "myobject.h" + +napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + napi_value instance; + NODE_API_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NODE_API_CALL(env, MyObject::Init(env)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_GETTER("finalizeCount", MyObject::GetFinalizeCount), + DECLARE_NODE_API_PROPERTY("createObject", CreateObject), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/7_factory_wrap/CMakeLists.txt new file mode 100644 index 00000000..1ca18670 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_module(7_factory_wrap + SOURCES + 7_factory_wrap.cc + myobject.cc +) diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/binding.gyp b/Tests/NodeApi/test/js-native-api/7_factory_wrap/binding.gyp new file mode 100644 index 00000000..f51f7823 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "7_factory_wrap", + "sources": [ + "7_factory_wrap.cc", + "myobject.cc" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.cc b/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.cc new file mode 100644 index 00000000..142c2dab --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.cc @@ -0,0 +1,101 @@ +#include "myobject.h" +#include "../common.h" + +static int finalize_count = 0; + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(node_api_basic_env env, + void* nativeObject, + void* /*finalize_hint*/) { + ++finalize_count; + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +napi_value MyObject::GetFinalizeCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalize_count, &result)); + return result; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("plusOne", PlusOne), + }; + + napi_value cons; + status = napi_define_class( + env, "MyObject", -1, New, nullptr, 1, properties, &cons); + if (status != napi_ok) return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + MyObject* obj = new MyObject(); + + if (valuetype == napi_undefined) { + obj->counter_ = 0; + } else { + NODE_API_CALL(env, napi_get_value_uint32(env, args[0], &obj->counter_)); + } + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap( + env, _this, obj, MyObject::Destructor, nullptr /* finalize_hint */, + &obj->wrapper_)); + + return _this; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->counter_ += 1; + + napi_value num; + NODE_API_CALL(env, napi_create_uint32(env, obj->counter_, &num)); + + return num; +} diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.h b/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.h new file mode 100644 index 00000000..aa2b199a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/myobject.h @@ -0,0 +1,27 @@ +#ifndef TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void + Destructor(node_api_basic_env env, void *nativeObject, void *finalize_hint); + static napi_value GetFinalizeCount(napi_env env, napi_callback_info info); + static napi_status + NewInstance(napi_env env, napi_value arg, napi_value *instance); + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + uint32_t counter_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_7_FACTORY_WRAP_MYOBJECT_H_ diff --git a/Tests/NodeApi/test/js-native-api/7_factory_wrap/test.js b/Tests/NodeApi/test/js-native-api/7_factory_wrap/test.js new file mode 100644 index 00000000..23840b36 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/7_factory_wrap/test.js @@ -0,0 +1,27 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); +const test = require(`./build/${common.buildType}/7_factory_wrap`); +const { gcUntil } = require('../../common/gc'); + +assert.strictEqual(test.finalizeCount, 0); +async function runGCTests() { + (() => { + const obj = test.createObject(10); + assert.strictEqual(obj.plusOne(), 11); + assert.strictEqual(obj.plusOne(), 12); + assert.strictEqual(obj.plusOne(), 13); + })(); + await gcUntil('test 1', () => (test.finalizeCount === 1)); + + (() => { + const obj2 = test.createObject(20); + assert.strictEqual(obj2.plusOne(), 21); + assert.strictEqual(obj2.plusOne(), 22); + assert.strictEqual(obj2.plusOne(), 23); + })(); + await gcUntil('test 2', () => (test.finalizeCount === 2)); +} +runGCTests(); diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/8_passing_wrapped.cc b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/8_passing_wrapped.cc new file mode 100644 index 00000000..f3328d93 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/8_passing_wrapped.cc @@ -0,0 +1,61 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "myobject.h" + +extern size_t finalize_count; + +static napi_value CreateObject(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + napi_value instance; + NODE_API_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +static napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + MyObject* obj1; + NODE_API_CALL(env, + napi_unwrap(env, args[0], reinterpret_cast(&obj1))); + + MyObject* obj2; + NODE_API_CALL(env, + napi_unwrap(env, args[1], reinterpret_cast(&obj2))); + + napi_value sum; + NODE_API_CALL(env, napi_create_double(env, obj1->Val() + obj2->Val(), &sum)); + + return sum; +} + +static napi_value FinalizeCount(napi_env env, napi_callback_info info) { + napi_value return_value; + NODE_API_CALL(env, napi_create_uint32(env, finalize_count, &return_value)); + return return_value; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + MyObject::Init(env); + + napi_property_descriptor desc[] = { + DECLARE_NODE_API_PROPERTY("createObject", CreateObject), + DECLARE_NODE_API_PROPERTY("add", Add), + DECLARE_NODE_API_PROPERTY("finalizeCount", FinalizeCount), + }; + + NODE_API_CALL(env, + napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/CMakeLists.txt new file mode 100644 index 00000000..3ee22eb8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_module(8_passing_wrapped + SOURCES + 8_passing_wrapped.cc + myobject.cc +) diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/binding.gyp b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/binding.gyp new file mode 100644 index 00000000..d043d0f5 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "8_passing_wrapped", + "sources": [ + "8_passing_wrapped.cc", + "myobject.cc" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.cc b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.cc new file mode 100644 index 00000000..ff352d3f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.cc @@ -0,0 +1,91 @@ +#include "myobject.h" +#include "../common.h" + +size_t finalize_count = 0; + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + finalize_count++; + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor( + node_api_basic_env env, + void *nativeObject, + void * /*finalize_hint*/) { + MyObject *obj = static_cast(nativeObject); + delete obj; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = + napi_define_class(env, "MyObject", -1, New, nullptr, 0, nullptr, &cons); + if (status != napi_ok) + return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) + return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject *obj = new MyObject(); + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype == napi_undefined) { + obj->val_ = 0; + } else { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &obj->val_)); + } + + obj->env_ = env; + + // The below call to napi_wrap() must request a reference to the wrapped + // object via the out-parameter, because this ensures that we test the code + // path that deals with a reference that is destroyed from its own finalizer. + NODE_API_CALL( + env, + napi_wrap( + env, + _this, + obj, + MyObject::Destructor, + nullptr /* finalize_hint */, + &obj->wrapper_)); + + return _this; +} + +napi_status +MyObject::NewInstance(napi_env env, napi_value arg, napi_value *instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) + return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) + return status; + + return napi_ok; +} diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.h b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.h new file mode 100644 index 00000000..bdde3fb4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/myobject.h @@ -0,0 +1,28 @@ +#ifndef TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void + Destructor(node_api_basic_env env, void *nativeObject, void *finalize_hint); + static napi_status + NewInstance(napi_env env, napi_value arg, napi_value *instance); + double Val() const { + return val_; + } + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + double val_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_8_PASSING_WRAPPED_MYOBJECT_H_ diff --git a/Tests/NodeApi/test/js-native-api/8_passing_wrapped/test.js b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/test.js new file mode 100644 index 00000000..145828e6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/8_passing_wrapped/test.js @@ -0,0 +1,21 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/8_passing_wrapped`); +const { gcUntil } = require('../../common/gc'); + +async function runTest() { + let obj1 = addon.createObject(10); + let obj2 = addon.createObject(20); + const result = addon.add(obj1, obj2); + assert.strictEqual(result, 30); + + // Make sure the native destructor gets called. + obj1 = null; + obj2 = null; + await gcUntil('8_passing_wrapped', + () => (addon.finalizeCount() === 2)); +} +runTest(); diff --git a/Tests/NodeApi/test/js-native-api/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/CMakeLists.txt new file mode 100644 index 00000000..a2426f51 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/CMakeLists.txt @@ -0,0 +1,7 @@ +if(JSR_NODE_API_BUILD_NATIVE_TESTS) + foreach(NODE_API_TEST_DIR ${JSR_NODE_API_NATIVE_TEST_DIRS}) + if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${NODE_API_TEST_DIR}/CMakeLists.txt) + add_subdirectory(${NODE_API_TEST_DIR}) + endif() + endforeach() +endif() diff --git a/Tests/NodeApi/test/js-native-api/common-inl.h b/Tests/NodeApi/test/js-native-api/common-inl.h new file mode 100644 index 00000000..2a1a8fa6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/common-inl.h @@ -0,0 +1,71 @@ +#ifndef JS_NATIVE_API_COMMON_INL_H_ +#define JS_NATIVE_API_COMMON_INL_H_ + +#include +#include "common.h" + +#include + +inline void add_returned_status(napi_env env, + const char* key, + napi_value object, + const char* expected_message, + napi_status expected_status, + napi_status actual_status) { + char napi_message_string[100] = ""; + napi_value prop_value; + + if (actual_status != expected_status) { + snprintf(napi_message_string, + sizeof(napi_message_string), + "Invalid status [%d]", + actual_status); + } + + NODE_API_CALL_RETURN_VOID( + env, + napi_create_string_utf8( + env, + (actual_status == expected_status ? expected_message + : napi_message_string), + NAPI_AUTO_LENGTH, + &prop_value)); + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, object, key, prop_value)); +} + +inline void add_last_status(napi_env env, + const char* key, + napi_value return_value) { + napi_value prop_value; + napi_value exception; + const napi_extended_error_info* p_last_error; + NODE_API_CALL_RETURN_VOID(env, napi_get_last_error_info(env, &p_last_error)); + // Content of p_last_error can be updated in subsequent node-api calls. + // Retrieve it immediately. + const char* error_message = p_last_error->error_message == NULL + ? "napi_ok" + : p_last_error->error_message; + + bool is_exception_pending; + NODE_API_CALL_RETURN_VOID( + env, napi_is_exception_pending(env, &is_exception_pending)); + if (is_exception_pending) { + NODE_API_CALL_RETURN_VOID( + env, napi_get_and_clear_last_exception(env, &exception)); + char exception_key[50]; + snprintf(exception_key, sizeof(exception_key), "%s%s", key, "Exception"); + NODE_API_CALL_RETURN_VOID( + env, + napi_set_named_property(env, return_value, exception_key, exception)); + } + + NODE_API_CALL_RETURN_VOID( + env, + napi_create_string_utf8( + env, error_message, NAPI_AUTO_LENGTH, &prop_value)); + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, return_value, key, prop_value)); +} + +#endif // JS_NATIVE_API_COMMON_INL_H_ diff --git a/Tests/NodeApi/test/js-native-api/common.h b/Tests/NodeApi/test/js-native-api/common.h new file mode 100644 index 00000000..7c99da88 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/common.h @@ -0,0 +1,132 @@ +#ifndef JS_NATIVE_API_COMMON_H_ +#define JS_NATIVE_API_COMMON_H_ + +#include +#include // abort() + +// Empty value so that macros here are able to return NULL or void +#define NODE_API_RETVAL_NOTHING // Intentionally blank #define + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info *error_info; \ + napi_get_last_error_info((env), &error_info); \ + bool is_pending; \ + const char* err_message = error_info->error_message; \ + napi_is_exception_pending((env), &is_pending); \ + /* If an exception is already pending, don't rethrow it */ \ + if (!is_pending) { \ + const char* error_message = err_message != NULL ? \ + err_message : \ + "empty error message"; \ + napi_throw_error((env), NULL, error_message); \ + } \ + } while (0) + +// The basic version of GET_AND_THROW_LAST_ERROR. We cannot access any +// exceptions and we cannot fail by way of JS exception, so we abort. +#define FATALLY_FAIL_WITH_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* error_info; \ + napi_get_last_error_info((env), &error_info); \ + const char* err_message = error_info->error_message; \ + const char* error_message = \ + err_message != NULL ? err_message : "empty error message"; \ + fprintf(stderr, "%s\n", error_message); \ + abort(); \ + } while (0) + +#define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), \ + NULL, \ + "assertion (" #assertion ") failed: " message); \ + return ret_val; \ + } \ + } while (0) + +#define NODE_API_BASIC_ASSERT_BASE(assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + fprintf(stderr, "assertion (" #assertion ") failed: " message); \ + abort(); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_ASSERT(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NULL) + +// Returns empty on failed assertion. +// This is meant to be used inside functions with void return type. +#define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING) + +#define NODE_API_BASIC_ASSERT_RETURN_VOID(assertion, message) \ + NODE_API_BASIC_ASSERT_BASE(assertion, message, NODE_API_RETVAL_NOTHING) + +#define NODE_API_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + +#define NODE_API_BASIC_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + FATALLY_FAIL_WITH_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL if the_call doesn't return napi_ok. +#define NODE_API_CALL(env, the_call) \ + NODE_API_CALL_BASE(env, the_call, NULL) + +// Returns empty if the_call doesn't return napi_ok. +#define NODE_API_CALL_RETURN_VOID(env, the_call) \ + NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + +#define NODE_API_BASIC_CALL_RETURN_VOID(env, the_call) \ + NODE_API_BASIC_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + +#define NODE_API_CHECK_STATUS(the_call) \ + do { \ + napi_status status = (the_call); \ + if (status != napi_ok) { \ + return status; \ + } \ + } while (0) + +#define NODE_API_ASSERT_STATUS(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure) + +#define DECLARE_NODE_API_PROPERTY(name, func) \ + { (name), NULL, (func), NULL, NULL, NULL, napi_default, NULL } + +#define DECLARE_NODE_API_GETTER(name, func) \ + { (name), NULL, NULL, (func), NULL, NULL, napi_default, NULL } + +#define DECLARE_NODE_API_PROPERTY_VALUE(name, value) \ + { (name), NULL, NULL, NULL, NULL, (value), napi_default, NULL } + +static inline void add_returned_status(napi_env env, + const char* key, + napi_value object, + const char* expected_message, + napi_status expected_status, + napi_status actual_status); + +static inline void add_last_status(napi_env env, + const char* key, + napi_value return_value); + +#include "common-inl.h" + +#endif // JS_NATIVE_API_COMMON_H_ diff --git a/Tests/NodeApi/test/js-native-api/entry_point.h b/Tests/NodeApi/test/js-native-api/entry_point.h new file mode 100644 index 00000000..2e74d6c0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/entry_point.h @@ -0,0 +1,12 @@ +#ifndef JS_NATIVE_API_ENTRY_POINT_H_ +#define JS_NATIVE_API_ENTRY_POINT_H_ + +#include + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports); +EXTERN_C_END + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) + +#endif // JS_NATIVE_API_ENTRY_POINT_H_ diff --git a/Tests/NodeApi/test/js-native-api/test_array/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_array/CMakeLists.txt new file mode 100644 index 00000000..bda269e6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_array/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_array + SOURCES + test_array.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_array/binding.gyp b/Tests/NodeApi/test/js-native-api/test_array/binding.gyp new file mode 100644 index 00000000..69545b66 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_array/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_array", + "sources": [ + "test_array.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_array/test.js b/Tests/NodeApi/test/js-native-api/test_array/test.js new file mode 100644 index 00000000..26bcb18f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_array/test.js @@ -0,0 +1,61 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_array = require(`./build/${common.buildType}/test_array`); + +const array = [ + 1, + 9, + 48, + 13493, + 9459324, + { name: 'hello' }, + [ + 'world', + 'node', + 'abi', + ], +]; + +assert.throws( + () => { + test_array.TestGetElement(array, array.length + 1); + }, + /^Error: assertion \(\(\(uint32_t\)index < length\)\) failed: Index out of bounds!$/, +); + +assert.throws( + () => { + test_array.TestGetElement(array, -2); + }, + /^Error: assertion \(index >= 0\) failed: Invalid index\. Expects a positive integer\.$/, +); + +array.forEach(function(element, index) { + assert.strictEqual(test_array.TestGetElement(array, index), element); +}); + + +assert.deepStrictEqual(test_array.New(array), array); + +assert(test_array.TestHasElement(array, 0)); +assert.strictEqual(test_array.TestHasElement(array, array.length + 1), false); + +assert(test_array.NewWithLength(0) instanceof Array); +assert(test_array.NewWithLength(1) instanceof Array); +// Check max allowed length for an array 2^32 -1 +// TODO: Hermes does not allow such big arrays +// assert(test_array.NewWithLength(4294967295) instanceof Array); + +{ + // Verify that array elements can be deleted. + const arr = ['a', 'b', 'c', 'd']; + + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, true); + assert.strictEqual(test_array.TestDeleteElement(arr, 2), true); + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, false); +} diff --git a/Tests/NodeApi/test/js-native-api/test_array/test_array.c b/Tests/NodeApi/test/js-native-api/test_array/test_array.c new file mode 100644 index 00000000..7a34af20 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_array/test_array.c @@ -0,0 +1,188 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestGetElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + + NODE_API_ASSERT(env, index >= 0, "Invalid index. Expects a positive integer."); + + bool isarray; + NODE_API_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + uint32_t length; + NODE_API_CALL(env, napi_get_array_length(env, array, &length)); + + NODE_API_ASSERT(env, ((uint32_t)index < length), "Index out of bounds!"); + + napi_value ret; + NODE_API_CALL(env, napi_get_element(env, array, index, &ret)); + + return ret; +} + +static napi_value TestHasElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + + bool isarray; + NODE_API_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + bool has_element; + NODE_API_CALL(env, napi_has_element(env, array, index, &has_element)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, has_element, &ret)); + + return ret; +} + +static napi_value TestDeleteElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + bool result; + napi_value ret; + + NODE_API_CALL(env, napi_get_value_int32(env, args[1], &index)); + NODE_API_CALL(env, napi_is_array(env, array, &result)); + + if (!result) { + return NULL; + } + + NODE_API_CALL(env, napi_delete_element(env, array, index, &result)); + NODE_API_CALL(env, napi_get_boolean(env, result, &ret)); + + return ret; +} + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_value ret; + NODE_API_CALL(env, napi_create_array(env, &ret)); + + uint32_t i, length; + NODE_API_CALL(env, napi_get_array_length(env, args[0], &length)); + + for (i = 0; i < length; i++) { + napi_value e; + NODE_API_CALL(env, napi_get_element(env, args[0], i, &e)); + NODE_API_CALL(env, napi_set_element(env, ret, i, e)); + } + + return ret; +} + +static napi_value NewWithLength(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects an integer the first argument."); + + int32_t array_length; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &array_length)); + + napi_value ret; + NODE_API_CALL(env, napi_create_array_with_length(env, array_length, &ret)); + + return ret; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("TestGetElement", TestGetElement), + DECLARE_NODE_API_PROPERTY("TestHasElement", TestHasElement), + DECLARE_NODE_API_PROPERTY("TestDeleteElement", TestDeleteElement), + DECLARE_NODE_API_PROPERTY("New", New), + DECLARE_NODE_API_PROPERTY("NewWithLength", NewWithLength), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_bigint/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_bigint/CMakeLists.txt new file mode 100644 index 00000000..7027e3be --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_bigint/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_bigint + SOURCES + test_bigint.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_bigint/binding.gyp b/Tests/NodeApi/test/js-native-api/test_bigint/binding.gyp new file mode 100644 index 00000000..6dc71015 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_bigint/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_bigint", + "sources": [ + "test_bigint.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_bigint/test.js b/Tests/NodeApi/test/js-native-api/test_bigint/test.js new file mode 100644 index 00000000..50febf14 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_bigint/test.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const { + IsLossless, + TestInt64, + TestUint64, + TestWords, + CreateTooBigBigInt, + MakeBigIntWordsThrow, +} = require(`./build/${common.buildType}/test_bigint`); + +[ + 0n, + -0n, + 1n, + -1n, + 100n, + 2121n, + -1233n, + 986583n, + -976675n, + 98765432213456789876546896323445679887645323232436587988766545658n, + -4350987086545760976737453646576078997096876957864353245245769809n, +].forEach((num) => { + if (num > -(2n ** 63n) && num < 2n ** 63n) { + assert.strictEqual(TestInt64(num), num); + assert.strictEqual(IsLossless(num, true), true); + } else { + assert.strictEqual(IsLossless(num, true), false); + } + + if (num >= 0 && num < 2n ** 64n) { + assert.strictEqual(TestUint64(num), num); + assert.strictEqual(IsLossless(num, false), true); + } else { + assert.strictEqual(IsLossless(num, false), false); + } + + assert.strictEqual(num, TestWords(num)); +}); + +assert.throws(() => CreateTooBigBigInt(), { + name: 'Error', + message: 'Invalid argument', +}); + +// Test that we correctly forward exceptions from the engine. +assert.throws(() => MakeBigIntWordsThrow(), { + name: 'RangeError', + message: 'Maximum BigInt size exceeded', +}); diff --git a/Tests/NodeApi/test/js-native-api/test_bigint/test_bigint.c b/Tests/NodeApi/test/js-native-api/test_bigint/test_bigint.c new file mode 100644 index 00000000..203bc3a7 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_bigint/test_bigint.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value IsLossless(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + bool is_signed; + NODE_API_CALL(env, napi_get_value_bool(env, args[1], &is_signed)); + + bool lossless; + + if (is_signed) { + int64_t input; + NODE_API_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + } else { + uint64_t input; + NODE_API_CALL(env, napi_get_value_bigint_uint64(env, args[0], &input, &lossless)); + } + + napi_value output; + NODE_API_CALL(env, napi_get_boolean(env, lossless, &output)); + + return output; +} + +static napi_value TestInt64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + int64_t input; + bool lossless; + NODE_API_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_int64(env, input, &output)); + + return output; +} + +static napi_value TestUint64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + uint64_t input; + bool lossless; + NODE_API_CALL(env, napi_get_value_bigint_uint64( + env, args[0], &input, &lossless)); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_uint64(env, input, &output)); + + return output; +} + +static napi_value TestWords(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + size_t expected_word_count; + NODE_API_CALL(env, napi_get_value_bigint_words( + env, args[0], NULL, &expected_word_count, NULL)); + + int sign_bit; + size_t word_count = 10; + uint64_t words[10]; + + NODE_API_CALL(env, napi_get_value_bigint_words( + env, args[0], &sign_bit, &word_count, words)); + + NODE_API_ASSERT(env, word_count == expected_word_count, + "word counts do not match"); + + napi_value output; + NODE_API_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +// throws RangeError +static napi_value CreateTooBigBigInt(napi_env env, napi_callback_info info) { + int sign_bit = 0; + size_t word_count = SIZE_MAX; + uint64_t words[10] = {0}; + + napi_value output; + + NODE_API_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +// Test that we correctly forward exceptions from the engine. +static napi_value MakeBigIntWordsThrow(napi_env env, napi_callback_info info) { + uint64_t words[10] = {0}; + napi_value output; + + napi_status status = napi_create_bigint_words(env, + 0, + INT_MAX, + words, + &output); + if (status != napi_pending_exception) + napi_throw_error(env, NULL, "Expected status `napi_pending_exception`"); + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("IsLossless", IsLossless), + DECLARE_NODE_API_PROPERTY("TestInt64", TestInt64), + DECLARE_NODE_API_PROPERTY("TestUint64", TestUint64), + DECLARE_NODE_API_PROPERTY("TestWords", TestWords), + DECLARE_NODE_API_PROPERTY("CreateTooBigBigInt", CreateTooBigBigInt), + DECLARE_NODE_API_PROPERTY("MakeBigIntWordsThrow", MakeBigIntWordsThrow), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_cannot_run_js/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/CMakeLists.txt new file mode 100644 index 00000000..c708e9cb --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/CMakeLists.txt @@ -0,0 +1,13 @@ +add_node_api_module(test_cannot_run_js + SOURCES + test_cannot_run_js.c + DEFINES + "NAPI_VERSION=10" +) + +add_node_api_module(test_pending_exception + SOURCES + test_cannot_run_js.c + DEFINES + "NAPI_VERSION=9" +) diff --git a/Tests/NodeApi/test/js-native-api/test_cannot_run_js/binding.gyp b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/binding.gyp new file mode 100644 index 00000000..51ff8ccb --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/binding.gyp @@ -0,0 +1,18 @@ +{ + "targets": [ + { + "target_name": "test_cannot_run_js", + "sources": [ + "test_cannot_run_js.c" + ], + "defines": [ "NAPI_VERSION=10" ], + }, + { + "target_name": "test_pending_exception", + "sources": [ + "test_cannot_run_js.c" + ], + "defines": [ "NAPI_VERSION=9" ], + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test.js b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test.js new file mode 100644 index 00000000..31c82480 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test.js @@ -0,0 +1,24 @@ +'use strict'; + +// Test that `napi_call_function()` returns `napi_cannot_run_js` in experimental +// mode and `napi_pending_exception` otherwise. This test calls the add-on's +// `createRef()` method, which creates a strong reference to a JS function. When +// the process exits, it calls all reference finalizers. The finalizer for the +// strong reference created herein will attempt to call `napi_get_property()` on +// a property of the global object and will abort the process if the API doesn't +// return the correct status. + +const { buildType, mustNotCall } = require('../../common'); +const addon_v8 = require(`./build/${buildType}/test_pending_exception`); +const addon_new = require(`./build/${buildType}/test_cannot_run_js`); + +function runTests(addon, isVersion8) { + addon.createRef(mustNotCall()); +} + +function runAllTests() { + runTests(addon_v8, /* isVersion8 */ true); + runTests(addon_new, /* isVersion8 */ false); +} + +runAllTests(); diff --git a/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c new file mode 100644 index 00000000..8ca44c23 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c @@ -0,0 +1,66 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "stdlib.h" + +static void Finalize(napi_env env, void* data, void* hint) { + napi_value global, set_timeout; + napi_ref* ref = data; + + NODE_API_BASIC_ASSERT_RETURN_VOID( + napi_delete_reference(env, *ref) == napi_ok, + "deleting reference in finalizer should succeed"); + NODE_API_BASIC_ASSERT_RETURN_VOID( + napi_get_global(env, &global) == napi_ok, + "getting global reference in finalizer should succeed"); + napi_status result = + napi_get_named_property(env, global, "setTimeout", &set_timeout); + + // The finalizer could be invoked either from check callbacks (as native + // immediates) if the event loop is still running (where napi_ok is returned) + // or during environment shutdown (where napi_cannot_run_js or + // napi_pending_exception is returned). This is not deterministic from + // the point of view of the addon. + +#if NAPI_VERSION > 9 + NODE_API_BASIC_ASSERT_RETURN_VOID( + result == napi_cannot_run_js || result == napi_ok, + "getting named property from global in finalizer should succeed " + "or return napi_cannot_run_js"); +#else + NODE_API_BASIC_ASSERT_RETURN_VOID( + result == napi_pending_exception || result == napi_ok, + "getting named property from global in finalizer should succeed " + "or return napi_pending_exception"); +#endif // NAPI_VERSION > 9 + free(ref); +} + +static napi_value CreateRef(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value cb; + napi_valuetype value_type; + napi_ref* ref = malloc(sizeof(*ref)); + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Function takes only one argument"); + NODE_API_CALL(env, napi_typeof(env, cb, &value_type)); + NODE_API_ASSERT( + env, value_type == napi_function, "argument must be function"); + NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, Finalize, NULL, ref)); + return cb; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), + }; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_constructor/CMakeLists.txt new file mode 100644 index 00000000..0582345e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_module(test_constructor + SOURCES + test_constructor.c + test_null.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/binding.gyp b/Tests/NodeApi/test/js-native-api/test_constructor/binding.gyp new file mode 100644 index 00000000..af0c5d10 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_constructor", + "sources": [ + "test_constructor.c", + "test_null.c", + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test.js b/Tests/NodeApi/test/js-native-api/test_constructor/test.js new file mode 100644 index 00000000..4ef41794 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test.js @@ -0,0 +1,62 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +const getterOnlyErrorRE = + /^TypeError: Cannot (set|assign to) property .*( of #<.*>)? which has only a getter$/; + +// Testing api calls for a constructor that defines properties +const TestConstructor = require(`./build/${common.buildType}/test_constructor`); +const test_object = new TestConstructor(); + +assert.strictEqual(test_object.echo('hello'), 'hello'); + +test_object.readwriteValue = 1; +assert.strictEqual(test_object.readwriteValue, 1); +test_object.readwriteValue = 2; +assert.strictEqual(test_object.readwriteValue, 2); + +assert.throws(() => { test_object.readonlyValue = 3; }, + /^TypeError: Cannot assign to read(-| )only property 'readonlyValue'.*(of object '#')?/); + +assert.ok(test_object.hiddenValue); + +// Properties with napi_enumerable attribute should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.includes('echo')); +assert.ok(propertyNames.includes('readwriteValue')); +assert.ok(propertyNames.includes('readonlyValue')); +assert.ok(!propertyNames.includes('hiddenValue')); +assert.ok(!propertyNames.includes('readwriteAccessor1')); +assert.ok(!propertyNames.includes('readwriteAccessor2')); +assert.ok(!propertyNames.includes('readonlyAccessor1')); +assert.ok(!propertyNames.includes('readonlyAccessor2')); + +// The napi_writable attribute should be ignored for accessors. +test_object.readwriteAccessor1 = 1; +assert.strictEqual(test_object.readwriteAccessor1, 1); +assert.strictEqual(test_object.readonlyAccessor1, 1); +assert.throws(() => { test_object.readonlyAccessor1 = 3; }, getterOnlyErrorRE); +test_object.readwriteAccessor2 = 2; +assert.strictEqual(test_object.readwriteAccessor2, 2); +assert.strictEqual(test_object.readonlyAccessor2, 2); +assert.throws(() => { test_object.readonlyAccessor2 = 3; }, getterOnlyErrorRE); + +// Validate that static properties are on the class as opposed +// to the instance +assert.strictEqual(TestConstructor.staticReadonlyAccessor1, 10); +assert.strictEqual(test_object.staticReadonlyAccessor1, undefined); + +// Verify that passing NULL to napi_define_class() results in the correct +// error. +assert.deepStrictEqual(TestConstructor.TestDefineClass(), { + envIsNull: 'Invalid argument', + nameIsNull: 'Invalid argument', + cbIsNull: 'Invalid argument', + cbDataIsNull: 'napi_ok', + propertiesIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument' +}); diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test2.js b/Tests/NodeApi/test/js-native-api/test_constructor/test2.js new file mode 100644 index 00000000..125af81c --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test2.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for a constructor that defines properties +const TestConstructor = + require(`./build/${common.buildType}/test_constructor`).constructorName; +assert.strictEqual(TestConstructor.name, 'MyObject'); diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test_constructor.c b/Tests/NodeApi/test/js-native-api/test_constructor/test_constructor.c new file mode 100644 index 00000000..0c52bc31 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test_constructor.c @@ -0,0 +1,200 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +static double value_ = 1; +static double static_value_ = 10; + +static napi_value TestDefineClass(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value result, return_value; + + napi_property_descriptor property_descriptor = { + "TestDefineClass", + NULL, + TestDefineClass, + NULL, + NULL, + NULL, + napi_enumerable | napi_static, + NULL}; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + status = napi_define_class(NULL, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestDefineClass, + NULL, + 1, + &property_descriptor, + &result); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + status); + + napi_define_class(env, + NULL, + NAPI_AUTO_LENGTH, + TestDefineClass, + NULL, + 1, + &property_descriptor, + &result); + + add_last_status(env, "nameIsNull", return_value); + + napi_define_class(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + NULL, + NULL, + 1, + &property_descriptor, + &result); + + add_last_status(env, "cbIsNull", return_value); + + napi_define_class(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestDefineClass, + NULL, + 1, + &property_descriptor, + &result); + + add_last_status(env, "cbDataIsNull", return_value); + + napi_define_class(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestDefineClass, + NULL, + 1, + NULL, + &result); + + add_last_status(env, "propertiesIsNull", return_value); + + + napi_define_class(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestDefineClass, + NULL, + 1, + &property_descriptor, + NULL); + + add_last_status(env, "resultIsNull", return_value); + + return return_value; +} + +static napi_value GetValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + return number; +} + +static napi_value SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value_)); + + return NULL; +} + +static napi_value Echo(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + return args[0]; +} + +static napi_value New(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &_this, NULL)); + + return _this; +} + +static napi_value GetStaticValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NODE_API_CALL(env, napi_create_double(env, static_value_, &number)); + + return number; +} + + +static napi_value NewExtra(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &_this, NULL)); + + return _this; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value number, cons; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + NODE_API_CALL(env, napi_define_class( + env, "MyObject_Extra", 8, NewExtra, NULL, 0, NULL, &cons)); + + napi_property_descriptor properties[] = { + { "echo", NULL, Echo, NULL, NULL, NULL, napi_enumerable, NULL }, + { "readwriteValue", NULL, NULL, NULL, NULL, number, + napi_enumerable | napi_writable, NULL }, + { "readonlyValue", NULL, NULL, NULL, NULL, number, napi_enumerable, + NULL }, + { "hiddenValue", NULL, NULL, NULL, NULL, number, napi_default, NULL }, + { "readwriteAccessor1", NULL, NULL, GetValue, SetValue, NULL, napi_default, + NULL }, + { "readwriteAccessor2", NULL, NULL, GetValue, SetValue, NULL, + napi_writable, NULL }, + { "readonlyAccessor1", NULL, NULL, GetValue, NULL, NULL, napi_default, + NULL }, + { "readonlyAccessor2", NULL, NULL, GetValue, NULL, NULL, napi_writable, + NULL }, + { "staticReadonlyAccessor1", NULL, NULL, GetStaticValue, NULL, NULL, + napi_default | napi_static, NULL}, + { "constructorName", NULL, NULL, NULL, NULL, cons, + napi_enumerable | napi_static, NULL }, + { "TestDefineClass", NULL, TestDefineClass, NULL, NULL, NULL, + napi_enumerable | napi_static, NULL }, + }; + + NODE_API_CALL(env, napi_define_class(env, "MyObject", NAPI_AUTO_LENGTH, New, + NULL, sizeof(properties)/sizeof(*properties), properties, &cons)); + + init_test_null(env, cons); + + return cons; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test_null.c b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.c new file mode 100644 index 00000000..acbe5982 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.c @@ -0,0 +1,111 @@ +#include + +#include "../common.h" +#include "test_null.h" + +static int some_data = 0; + +static napi_value TestConstructor(napi_env env, napi_callback_info info) { + return NULL; +} + +static napi_value TestDefineClass(napi_env env, napi_callback_info info) { + napi_value return_value, cons; + + const napi_property_descriptor prop = + DECLARE_NODE_API_PROPERTY("testConstructor", TestConstructor); + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_define_class(NULL, + "TestClass", + NAPI_AUTO_LENGTH, + TestConstructor, + &some_data, + 1, + &prop, + &cons)); + + napi_define_class(env, + NULL, + NAPI_AUTO_LENGTH, + TestConstructor, + &some_data, + 1, + &prop, + &cons); + add_last_status(env, "nameIsNull", return_value); + + napi_define_class( + env, "TestClass", 0, TestConstructor, &some_data, 1, &prop, &cons); + add_last_status(env, "lengthIsZero", return_value); + + napi_define_class( + env, "TestClass", NAPI_AUTO_LENGTH, NULL, &some_data, 1, &prop, &cons); + add_last_status(env, "nativeSideIsNull", return_value); + + napi_define_class(env, + "TestClass", + NAPI_AUTO_LENGTH, + TestConstructor, + NULL, + 1, + &prop, + &cons); + add_last_status(env, "dataIsNull", return_value); + + napi_define_class(env, + "TestClass", + NAPI_AUTO_LENGTH, + TestConstructor, + &some_data, + 0, + &prop, + &cons); + add_last_status(env, "propsLengthIsZero", return_value); + + napi_define_class(env, + "TestClass", + NAPI_AUTO_LENGTH, + TestConstructor, + &some_data, + 1, + NULL, + &cons); + add_last_status(env, "propsIsNull", return_value); + + napi_define_class(env, + "TestClass", + NAPI_AUTO_LENGTH, + TestConstructor, + &some_data, + 1, + &prop, + NULL); + add_last_status(env, "resultIsNull", return_value); + + return return_value; +} + +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("testDefineClass", TestDefineClass), + }; + + NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &test_null)); + NODE_API_CALL_RETURN_VOID( + env, + napi_define_properties(env, + test_null, + sizeof(test_null_props) / sizeof(*test_null_props), + test_null_props)); + + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "testNull", test_null)); +} diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test_null.h b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.h new file mode 100644 index 00000000..b142570d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ diff --git a/Tests/NodeApi/test/js-native-api/test_constructor/test_null.js b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.js new file mode 100644 index 00000000..f944953e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_constructor/test_null.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Test passing NULL to object-related N-APIs. +const { testNull } = require(`./build/${common.buildType}/test_constructor`); +const expectedResult = { + envIsNull: 'Invalid argument', + nameIsNull: 'Invalid argument', + lengthIsZero: 'napi_ok', + nativeSideIsNull: 'Invalid argument', + dataIsNull: 'napi_ok', + propsLengthIsZero: 'napi_ok', + propsIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}; + +assert.deepStrictEqual(testNull.testDefineClass(), expectedResult); diff --git a/Tests/NodeApi/test/js-native-api/test_conversions/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_conversions/CMakeLists.txt new file mode 100644 index 00000000..732de7c6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_module(test_conversions + SOURCES + test_conversions.c + test_null.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_conversions/binding.gyp b/Tests/NodeApi/test/js-native-api/test_conversions/binding.gyp new file mode 100644 index 00000000..a7be5290 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_conversions", + "sources": [ + "test_conversions.c", + "test_null.c", + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_conversions/test.js b/Tests/NodeApi/test/js-native-api/test_conversions/test.js new file mode 100644 index 00000000..b5d047a4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/test.js @@ -0,0 +1,218 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test = require(`./build/${common.buildType}/test_conversions`); + +const boolExpected = /boolean was expected/; +const numberExpected = /number was expected/; +const stringExpected = /string was expected/; + +const testSym = Symbol('test'); + +assert.strictEqual(test.asBool(false), false); +assert.strictEqual(test.asBool(true), true); +assert.throws(() => test.asBool(undefined), boolExpected); +assert.throws(() => test.asBool(null), boolExpected); +assert.throws(() => test.asBool(Number.NaN), boolExpected); +assert.throws(() => test.asBool(0), boolExpected); +assert.throws(() => test.asBool(''), boolExpected); +assert.throws(() => test.asBool('0'), boolExpected); +assert.throws(() => test.asBool(1), boolExpected); +assert.throws(() => test.asBool('1'), boolExpected); +assert.throws(() => test.asBool('true'), boolExpected); +assert.throws(() => test.asBool({}), boolExpected); +assert.throws(() => test.asBool([]), boolExpected); +assert.throws(() => test.asBool(testSym), boolExpected); + +[test.asInt32, test.asUInt32, test.asInt64].forEach((asInt) => { + assert.strictEqual(asInt(0), 0); + assert.strictEqual(asInt(1), 1); + assert.strictEqual(asInt(1.0), 1); + assert.strictEqual(asInt(1.1), 1); + assert.strictEqual(asInt(1.9), 1); + assert.strictEqual(asInt(0.9), 0); + assert.strictEqual(asInt(999.9), 999); + assert.strictEqual(asInt(Number.NaN), 0); + assert.throws(() => asInt(undefined), numberExpected); + assert.throws(() => asInt(null), numberExpected); + assert.throws(() => asInt(false), numberExpected); + assert.throws(() => asInt(''), numberExpected); + assert.throws(() => asInt('1'), numberExpected); + assert.throws(() => asInt({}), numberExpected); + assert.throws(() => asInt([]), numberExpected); + assert.throws(() => asInt(testSym), numberExpected); +}); + +assert.strictEqual(test.asInt32(-1), -1); +assert.strictEqual(test.asInt64(-1), -1); +assert.strictEqual(test.asUInt32(-1), Math.pow(2, 32) - 1); + +assert.strictEqual(test.asDouble(0), 0); +assert.strictEqual(test.asDouble(1), 1); +assert.strictEqual(test.asDouble(1.0), 1.0); +assert.strictEqual(test.asDouble(1.1), 1.1); +assert.strictEqual(test.asDouble(1.9), 1.9); +assert.strictEqual(test.asDouble(0.9), 0.9); +assert.strictEqual(test.asDouble(999.9), 999.9); +assert.strictEqual(test.asDouble(-1), -1); +assert.ok(Number.isNaN(test.asDouble(Number.NaN))); +assert.throws(() => test.asDouble(undefined), numberExpected); +assert.throws(() => test.asDouble(null), numberExpected); +assert.throws(() => test.asDouble(false), numberExpected); +assert.throws(() => test.asDouble(''), numberExpected); +assert.throws(() => test.asDouble('1'), numberExpected); +assert.throws(() => test.asDouble({}), numberExpected); +assert.throws(() => test.asDouble([]), numberExpected); +assert.throws(() => test.asDouble(testSym), numberExpected); + +assert.strictEqual(test.asString(''), ''); +assert.strictEqual(test.asString('test'), 'test'); +assert.throws(() => test.asString(undefined), stringExpected); +assert.throws(() => test.asString(null), stringExpected); +assert.throws(() => test.asString(false), stringExpected); +assert.throws(() => test.asString(1), stringExpected); +assert.throws(() => test.asString(1.1), stringExpected); +assert.throws(() => test.asString(Number.NaN), stringExpected); +assert.throws(() => test.asString({}), stringExpected); +assert.throws(() => test.asString([]), stringExpected); +assert.throws(() => test.asString(testSym), stringExpected); + +assert.strictEqual(test.toBool(true), true); +assert.strictEqual(test.toBool(1), true); +assert.strictEqual(test.toBool(-1), true); +assert.strictEqual(test.toBool('true'), true); +assert.strictEqual(test.toBool('false'), true); +assert.strictEqual(test.toBool({}), true); +assert.strictEqual(test.toBool([]), true); +assert.strictEqual(test.toBool(testSym), true); +assert.strictEqual(test.toBool(false), false); +assert.strictEqual(test.toBool(undefined), false); +assert.strictEqual(test.toBool(null), false); +assert.strictEqual(test.toBool(0), false); +assert.strictEqual(test.toBool(Number.NaN), false); +assert.strictEqual(test.toBool(''), false); + +assert.strictEqual(test.toNumber(0), 0); +assert.strictEqual(test.toNumber(1), 1); +assert.strictEqual(test.toNumber(1.1), 1.1); +assert.strictEqual(test.toNumber(-1), -1); +assert.strictEqual(test.toNumber('0'), 0); +assert.strictEqual(test.toNumber('1'), 1); +assert.strictEqual(test.toNumber('1.1'), 1.1); +assert.strictEqual(test.toNumber([]), 0); +assert.strictEqual(test.toNumber(false), 0); +assert.strictEqual(test.toNumber(null), 0); +assert.strictEqual(test.toNumber(''), 0); +assert.ok(Number.isNaN(test.toNumber(Number.NaN))); +assert.ok(Number.isNaN(test.toNumber({}))); +assert.ok(Number.isNaN(test.toNumber(undefined))); +assert.throws(() => test.toNumber(testSym), TypeError); + +assert.deepStrictEqual({}, test.toObject({})); +assert.deepStrictEqual({ 'test': 1 }, test.toObject({ 'test': 1 })); +assert.deepStrictEqual([], test.toObject([])); +assert.deepStrictEqual([ 1, 2, 3 ], test.toObject([ 1, 2, 3 ])); +assert.deepStrictEqual(new Boolean(false), test.toObject(false)); +assert.deepStrictEqual(new Boolean(true), test.toObject(true)); +assert.deepStrictEqual(new String(''), test.toObject('')); +assert.deepStrictEqual(new Number(0), test.toObject(0)); +assert.deepStrictEqual(new Number(Number.NaN), test.toObject(Number.NaN)); +assert.deepStrictEqual(new Object(testSym), test.toObject(testSym)); +assert.notStrictEqual(test.toObject(false), false); +assert.notStrictEqual(test.toObject(true), true); +assert.notStrictEqual(test.toObject(''), ''); +assert.notStrictEqual(test.toObject(0), 0); +assert.ok(!Number.isNaN(test.toObject(Number.NaN))); + +assert.strictEqual(test.toString(''), ''); +assert.strictEqual(test.toString('test'), 'test'); +assert.strictEqual(test.toString(undefined), 'undefined'); +assert.strictEqual(test.toString(null), 'null'); +assert.strictEqual(test.toString(false), 'false'); +assert.strictEqual(test.toString(true), 'true'); +assert.strictEqual(test.toString(0), '0'); +assert.strictEqual(test.toString(1.1), '1.1'); +assert.strictEqual(test.toString(Number.NaN), 'NaN'); +assert.strictEqual(test.toString({}), '[object Object]'); +assert.strictEqual(test.toString({ toString: () => 'test' }), 'test'); +assert.strictEqual(test.toString([]), ''); +assert.strictEqual(test.toString([ 1, 2, 3 ]), '1,2,3'); +assert.throws(() => test.toString(testSym), TypeError); + +assert.deepStrictEqual(test.testNull.getValueBool(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'A boolean was expected', +}); + +assert.deepStrictEqual(test.testNull.getValueInt32(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'A number was expected', +}); + +assert.deepStrictEqual(test.testNull.getValueUint32(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'A number was expected', +}); + +assert.deepStrictEqual(test.testNull.getValueInt64(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'A number was expected', +}); + + +assert.deepStrictEqual(test.testNull.getValueDouble(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'A number was expected', +}); + +assert.deepStrictEqual(test.testNull.coerceToBool(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'napi_ok', +}); + +assert.deepStrictEqual(test.testNull.coerceToObject(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'napi_ok', +}); + +assert.deepStrictEqual(test.testNull.coerceToString(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + inputTypeCheck: 'napi_ok', +}); + +assert.deepStrictEqual(test.testNull.getValueStringUtf8(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + wrongTypeIn: 'A string was expected', + bufAndOutLengthIsNull: 'Invalid argument', +}); + +assert.deepStrictEqual(test.testNull.getValueStringLatin1(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + wrongTypeIn: 'A string was expected', + bufAndOutLengthIsNull: 'Invalid argument', +}); + +assert.deepStrictEqual(test.testNull.getValueStringUtf16(), { + envIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', + wrongTypeIn: 'A string was expected', + bufAndOutLengthIsNull: 'Invalid argument', +}); diff --git a/Tests/NodeApi/test/js-native-api/test_conversions/test_conversions.c b/Tests/NodeApi/test/js-native-api/test_conversions/test_conversions.c new file mode 100644 index 00000000..2db42970 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/test_conversions.c @@ -0,0 +1,158 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +static napi_value AsBool(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)); + + bool value; + NODE_API_CALL(env, napi_get_value_bool(env, args[0], &value)); + + napi_value output; + NODE_API_CALL(env, napi_get_boolean(env, value, &output)); + + return output; +} + +static napi_value AsInt32(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)); + + int32_t value; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &value)); + + napi_value output; + NODE_API_CALL(env, napi_create_int32(env, value, &output)); + + return output; +} + +static napi_value AsUInt32(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)); + + uint32_t value; + NODE_API_CALL(env, napi_get_value_uint32(env, args[0], &value)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, value, &output)); + + return output; +} + +static napi_value AsInt64(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)); + + int64_t value; + NODE_API_CALL(env, napi_get_value_int64(env, args[0], &value)); + + napi_value output; + NODE_API_CALL(env, napi_create_int64(env, (double)value, &output)); + + return output; +} + +static napi_value AsDouble(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)); + + double value; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value)); + + napi_value output; + NODE_API_CALL(env, napi_create_double(env, value, &output)); + + return output; +} + +static napi_value AsString(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)); + + char value[100]; + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[0], value, sizeof(value), NULL)); + + napi_value output; + NODE_API_CALL(env, napi_create_string_utf8( + env, value, NAPI_AUTO_LENGTH, &output)); + + return output; +} + +static napi_value ToBool(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 output; + NODE_API_CALL(env, napi_coerce_to_bool(env, args[0], &output)); + + return output; +} + +static napi_value ToNumber(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 output; + NODE_API_CALL(env, napi_coerce_to_number(env, args[0], &output)); + + return output; +} + +static napi_value ToObject(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 output; + NODE_API_CALL(env, napi_coerce_to_object(env, args[0], &output)); + + return output; +} + +static napi_value ToString(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 output; + NODE_API_CALL(env, napi_coerce_to_string(env, args[0], &output)); + + return output; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("asBool", AsBool), + DECLARE_NODE_API_PROPERTY("asInt32", AsInt32), + DECLARE_NODE_API_PROPERTY("asUInt32", AsUInt32), + DECLARE_NODE_API_PROPERTY("asInt64", AsInt64), + DECLARE_NODE_API_PROPERTY("asDouble", AsDouble), + DECLARE_NODE_API_PROPERTY("asString", AsString), + DECLARE_NODE_API_PROPERTY("toBool", ToBool), + DECLARE_NODE_API_PROPERTY("toNumber", ToNumber), + DECLARE_NODE_API_PROPERTY("toObject", ToObject), + DECLARE_NODE_API_PROPERTY("toString", ToString), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + init_test_null(env, exports); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_conversions/test_null.c b/Tests/NodeApi/test/js-native-api/test_conversions/test_null.c new file mode 100644 index 00000000..e08b986a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/test_null.c @@ -0,0 +1,102 @@ +#include + +#include "../common.h" +#include "test_null.h" + +#define GEN_NULL_CHECK_BINDING(binding_name, output_type, api) \ + static napi_value binding_name(napi_env env, napi_callback_info info) { \ + napi_value return_value; \ + output_type result; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + add_returned_status(env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + api(NULL, return_value, &result)); \ + api(env, NULL, &result); \ + add_last_status(env, "valueIsNull", return_value); \ + api(env, return_value, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + api(env, return_value, &result); \ + add_last_status(env, "inputTypeCheck", return_value); \ + return return_value; \ + } + +GEN_NULL_CHECK_BINDING(GetValueBool, bool, napi_get_value_bool) +GEN_NULL_CHECK_BINDING(GetValueInt32, int32_t, napi_get_value_int32) +GEN_NULL_CHECK_BINDING(GetValueUint32, uint32_t, napi_get_value_uint32) +GEN_NULL_CHECK_BINDING(GetValueInt64, int64_t, napi_get_value_int64) +GEN_NULL_CHECK_BINDING(GetValueDouble, double, napi_get_value_double) +GEN_NULL_CHECK_BINDING(CoerceToBool, napi_value, napi_coerce_to_bool) +GEN_NULL_CHECK_BINDING(CoerceToObject, napi_value, napi_coerce_to_object) +GEN_NULL_CHECK_BINDING(CoerceToString, napi_value, napi_coerce_to_string) + +#define GEN_NULL_CHECK_STRING_BINDING(binding_name, arg_type, api) \ + static napi_value binding_name(napi_env env, napi_callback_info info) { \ + napi_value return_value; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + arg_type buf1[4]; \ + size_t length1 = 3; \ + add_returned_status(env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + api(NULL, return_value, buf1, length1, &length1)); \ + arg_type buf2[4]; \ + size_t length2 = 3; \ + api(env, NULL, buf2, length2, &length2); \ + add_last_status(env, "valueIsNull", return_value); \ + api(env, return_value, NULL, 3, NULL); \ + add_last_status(env, "wrongTypeIn", return_value); \ + napi_value string; \ + NODE_API_CALL(env, \ + napi_create_string_utf8(env, \ + "Something", \ + NAPI_AUTO_LENGTH, \ + &string)); \ + api(env, string, NULL, 3, NULL); \ + add_last_status(env, "bufAndOutLengthIsNull", return_value); \ + return return_value; \ + } + +GEN_NULL_CHECK_STRING_BINDING(GetValueStringUtf8, + char, + napi_get_value_string_utf8) +GEN_NULL_CHECK_STRING_BINDING(GetValueStringLatin1, + char, + napi_get_value_string_latin1) +GEN_NULL_CHECK_STRING_BINDING(GetValueStringUtf16, + char16_t, + napi_get_value_string_utf16) + +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("getValueBool", GetValueBool), + DECLARE_NODE_API_PROPERTY("getValueInt32", GetValueInt32), + DECLARE_NODE_API_PROPERTY("getValueUint32", GetValueUint32), + DECLARE_NODE_API_PROPERTY("getValueInt64", GetValueInt64), + DECLARE_NODE_API_PROPERTY("getValueDouble", GetValueDouble), + DECLARE_NODE_API_PROPERTY("coerceToBool", CoerceToBool), + DECLARE_NODE_API_PROPERTY("coerceToObject", CoerceToObject), + DECLARE_NODE_API_PROPERTY("coerceToString", CoerceToString), + DECLARE_NODE_API_PROPERTY("getValueStringUtf8", GetValueStringUtf8), + DECLARE_NODE_API_PROPERTY("getValueStringLatin1", GetValueStringLatin1), + DECLARE_NODE_API_PROPERTY("getValueStringUtf16", GetValueStringUtf16), + }; + + 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/NodeApi/test/js-native-api/test_conversions/test_null.h b/Tests/NodeApi/test/js-native-api/test_conversions/test_null.h new file mode 100644 index 00000000..fe6ad77a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_conversions/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_CONVERSIONS_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_CONVERSIONS_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_CONVERSIONS_TEST_NULL_H_ diff --git a/Tests/NodeApi/test/js-native-api/test_dataview/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_dataview/CMakeLists.txt new file mode 100644 index 00000000..d809ba9a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_dataview/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_dataview + SOURCES + test_dataview.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_dataview/binding.gyp b/Tests/NodeApi/test/js-native-api/test_dataview/binding.gyp new file mode 100644 index 00000000..a8b4f1d4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_dataview/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_dataview", + "sources": [ + "test_dataview.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_dataview/test.js b/Tests/NodeApi/test/js-native-api/test_dataview/test.js new file mode 100644 index 00000000..2bfd109d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_dataview/test.js @@ -0,0 +1,24 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_dataview = require(`./build/${common.buildType}/test_dataview`); + +// Test for creating dataview +{ + const buffer = new ArrayBuffer(128); + const template = Reflect.construct(DataView, [buffer]); + + const theDataview = test_dataview.CreateDataViewFromJSDataView(template); + assert.ok(theDataview instanceof DataView, + `Expect ${theDataview} to be a DataView`); +} + +// Test for creating dataview with invalid range +{ + const buffer = new ArrayBuffer(128); + assert.throws(() => { + test_dataview.CreateDataView(buffer, 10, 200); + }, RangeError); +} diff --git a/Tests/NodeApi/test/js-native-api/test_dataview/test_dataview.c b/Tests/NodeApi/test/js-native-api/test_dataview/test_dataview.c new file mode 100644 index 00000000..20a840de --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_dataview/test_dataview.c @@ -0,0 +1,102 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value CreateDataView(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args [3]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 3, "Wrong number of arguments"); + + napi_valuetype valuetype0; + napi_value arraybuffer = args[0]; + + NODE_API_CALL(env, napi_typeof(env, arraybuffer, &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a ArrayBuffer as the first " + "argument."); + + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, arraybuffer, &is_arraybuffer)); + NODE_API_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects a ArrayBuffer as the first " + "argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + size_t byte_offset = 0; + NODE_API_CALL(env, napi_get_value_uint32(env, args[1], (uint32_t*)(&byte_offset))); + + napi_valuetype valuetype2; + NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NODE_API_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + size_t length = 0; + NODE_API_CALL(env, napi_get_value_uint32(env, args[2], (uint32_t*)(&length))); + + napi_value output_dataview; + NODE_API_CALL(env, + napi_create_dataview(env, length, arraybuffer, + byte_offset, &output_dataview)); + + return output_dataview; +} + +static napi_value CreateDataViewFromJSDataView(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 valuetype; + napi_value input_dataview = args[0]; + + NODE_API_CALL(env, napi_typeof(env, input_dataview, &valuetype)); + NODE_API_ASSERT(env, valuetype == napi_object, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + + bool is_dataview; + NODE_API_CALL(env, napi_is_dataview(env, input_dataview, &is_dataview)); + NODE_API_ASSERT(env, is_dataview, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + size_t byte_offset = 0; + size_t length = 0; + napi_value buffer; + NODE_API_CALL(env, + napi_get_dataview_info(env, input_dataview, &length, NULL, + &buffer, &byte_offset)); + + napi_value output_dataview; + NODE_API_CALL(env, + napi_create_dataview(env, length, buffer, + byte_offset, &output_dataview)); + + + return output_dataview; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("CreateDataView", CreateDataView), + DECLARE_NODE_API_PROPERTY("CreateDataViewFromJSDataView", + CreateDataViewFromJSDataView) + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_date/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_date/CMakeLists.txt new file mode 100644 index 00000000..9c9736c8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_date/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_date + SOURCES + test_date.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_date/binding.gyp b/Tests/NodeApi/test/js-native-api/test_date/binding.gyp new file mode 100644 index 00000000..e08eaf6d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_date/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_date", + "sources": [ + "test_date.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_date/test.js b/Tests/NodeApi/test/js-native-api/test_date/test.js new file mode 100644 index 00000000..637f9f94 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_date/test.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../../common'); + +// This tests the date-related n-api calls + +const assert = require('assert'); +const test_date = require(`./build/${common.buildType}/test_date`); + +const dateTypeTestDate = test_date.createDate(1549183351); +assert.strictEqual(test_date.isDate(dateTypeTestDate), true); + +assert.strictEqual(test_date.isDate(new Date(1549183351)), true); + +assert.strictEqual(test_date.isDate(2.4), false); +assert.strictEqual(test_date.isDate('not a date'), false); +assert.strictEqual(test_date.isDate(undefined), false); +assert.strictEqual(test_date.isDate(null), false); +assert.strictEqual(test_date.isDate({}), false); + +assert.strictEqual(test_date.getDateValue(new Date(1549183351)), 1549183351); diff --git a/Tests/NodeApi/test/js-native-api/test_date/test_date.c b/Tests/NodeApi/test/js-native-api/test_date/test_date.c new file mode 100644 index 00000000..a9eeb4f0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_date/test_date.c @@ -0,0 +1,64 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value createDate(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + double time; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &time)); + + napi_value date; + NODE_API_CALL(env, napi_create_date(env, time, &date)); + + return date; +} + +static napi_value isDate(napi_env env, napi_callback_info info) { + napi_value date, result; + size_t argc = 1; + bool is_date; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &date, NULL, NULL)); + NODE_API_CALL(env, napi_is_date(env, date, &is_date)); + NODE_API_CALL(env, napi_get_boolean(env, is_date, &result)); + + return result; +} + +static napi_value getDateValue(napi_env env, napi_callback_info info) { + napi_value date, result; + size_t argc = 1; + double value; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &date, NULL, NULL)); + NODE_API_CALL(env, napi_get_date_value(env, date, &value)); + NODE_API_CALL(env, napi_create_double(env, value, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("createDate", createDate), + DECLARE_NODE_API_PROPERTY("isDate", isDate), + DECLARE_NODE_API_PROPERTY("getDateValue", getDateValue), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_error/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_error/CMakeLists.txt new file mode 100644 index 00000000..955fd2a0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_error/CMakeLists.txt @@ -0,0 +1,6 @@ +add_node_api_module(test_error + SOURCES + test_error.c + DEFINES + "NAPI_VERSION=9" +) diff --git a/Tests/NodeApi/test/js-native-api/test_error/binding.gyp b/Tests/NodeApi/test/js-native-api/test_error/binding.gyp new file mode 100644 index 00000000..f0448028 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_error/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_error", + "sources": [ + "test_error.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_error/test.js b/Tests/NodeApi/test/js-native-api/test_error/test.js new file mode 100644 index 00000000..f6ba3799 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_error/test.js @@ -0,0 +1,148 @@ +'use strict'; + +const common = require('../../common'); +const test_error = require(`./build/${common.buildType}/test_error`); +const assert = require('assert'); +const theError = new Error('Some error'); +const theTypeError = new TypeError('Some type error'); +const theSyntaxError = new SyntaxError('Some syntax error'); +const theRangeError = new RangeError('Some type error'); +const theReferenceError = new ReferenceError('Some reference error'); +const theURIError = new URIError('Some URI error'); +const theEvalError = new EvalError('Some eval error'); + +class MyError extends Error { } +const myError = new MyError('Some MyError'); + +// Test that native error object is correctly classed +assert.strictEqual(test_error.checkError(theError), true); + +// Test that native type error object is correctly classed +assert.strictEqual(test_error.checkError(theTypeError), true); + +// Test that native syntax error object is correctly classed +assert.strictEqual(test_error.checkError(theSyntaxError), true); + +// Test that native range error object is correctly classed +assert.strictEqual(test_error.checkError(theRangeError), true); + +// Test that native reference error object is correctly classed +assert.strictEqual(test_error.checkError(theReferenceError), true); + +// Test that native URI error object is correctly classed +assert.strictEqual(test_error.checkError(theURIError), true); + +// Test that native eval error object is correctly classed +assert.strictEqual(test_error.checkError(theEvalError), true); + +// Test that class derived from native error is correctly classed +assert.strictEqual(test_error.checkError(myError), true); + +// Test that non-error object is correctly classed +assert.strictEqual(test_error.checkError({}), false); + +// Test that non-error primitive is correctly classed +assert.strictEqual(test_error.checkError('non-object'), false); + +assert.throws(() => { + test_error.throwExistingError(); +}, /^Error: existing error$/); + +assert.throws(() => { + test_error.throwError(); +}, /^Error: error$/); + +assert.throws(() => { + test_error.throwRangeError(); +}, /^RangeError: range error$/); + +assert.throws(() => { + test_error.throwTypeError(); +}, /^TypeError: type error$/); + +assert.throws(() => { + test_error.throwSyntaxError(); +}, /^SyntaxError: syntax error$/); + +[42, {}, [], Symbol('xyzzy'), true, 'ball', undefined, null, NaN] + .forEach((value) => assert.throws( + () => test_error.throwArbitrary(value), + (err) => { + assert.strictEqual(err, value); + return true; + }, + )); + +assert.throws( + () => test_error.throwErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'Error [error]', + }); + +assert.throws( + () => test_error.throwRangeErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'RangeError [range error]', + }); + +assert.throws( + () => test_error.throwTypeErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'TypeError [type error]', + }); + +assert.throws( + () => test_error.throwSyntaxErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'SyntaxError [syntax error]', + }); + +let error = test_error.createError(); +assert.ok(error instanceof Error, 'expected error to be an instance of Error'); +assert.strictEqual(error.message, 'error'); + +error = test_error.createRangeError(); +assert.ok(error instanceof RangeError, + 'expected error to be an instance of RangeError'); +assert.strictEqual(error.message, 'range error'); + +error = test_error.createTypeError(); +assert.ok(error instanceof TypeError, + 'expected error to be an instance of TypeError'); +assert.strictEqual(error.message, 'type error'); + +error = test_error.createSyntaxError(); +assert.ok(error instanceof SyntaxError, + 'expected error to be an instance of SyntaxError'); +assert.strictEqual(error.message, 'syntax error'); + +error = test_error.createErrorCode(); +assert.ok(error instanceof Error, 'expected error to be an instance of Error'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.message, 'Error [error]'); +assert.strictEqual(error.name, 'Error'); + +error = test_error.createRangeErrorCode(); +assert.ok(error instanceof RangeError, + 'expected error to be an instance of RangeError'); +assert.strictEqual(error.message, 'RangeError [range error]'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.name, 'RangeError'); + +error = test_error.createTypeErrorCode(); +assert.ok(error instanceof TypeError, + 'expected error to be an instance of TypeError'); +assert.strictEqual(error.message, 'TypeError [type error]'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.name, 'TypeError'); + +error = test_error.createSyntaxErrorCode(); +assert.ok(error instanceof SyntaxError, + 'expected error to be an instance of SyntaxError'); +assert.strictEqual(error.message, 'SyntaxError [syntax error]'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.name, 'SyntaxError'); diff --git a/Tests/NodeApi/test/js-native-api/test_error/test_error.c b/Tests/NodeApi/test/js-native-api/test_error/test_error.c new file mode 100644 index 00000000..fc4b8758 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_error/test_error.c @@ -0,0 +1,197 @@ +#define NAPI_VERSION 9 +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value checkError(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)); + + bool r; + NODE_API_CALL(env, napi_is_error(env, args[0], &r)); + + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, r, &result)); + + return result; +} + +static napi_value throwExistingError(napi_env env, napi_callback_info info) { + napi_value message; + napi_value error; + NODE_API_CALL(env, napi_create_string_utf8( + env, "existing error", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_error(env, NULL, message, &error)); + NODE_API_CALL(env, napi_throw(env, error)); + return NULL; +} + +static napi_value throwError(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, napi_throw_error(env, NULL, "error")); + return NULL; +} + +static napi_value throwRangeError(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, napi_throw_range_error(env, NULL, "range error")); + return NULL; +} + +static napi_value throwTypeError(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, napi_throw_type_error(env, NULL, "type error")); + return NULL; +} + +static napi_value throwSyntaxError(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, node_api_throw_syntax_error(env, NULL, "syntax error")); + return NULL; +} + +static napi_value throwErrorCode(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, napi_throw_error(env, "ERR_TEST_CODE", "Error [error]")); + return NULL; +} + +static napi_value throwRangeErrorCode(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, + napi_throw_range_error(env, "ERR_TEST_CODE", "RangeError [range error]")); + return NULL; +} + +static napi_value throwTypeErrorCode(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, + napi_throw_type_error(env, "ERR_TEST_CODE", "TypeError [type error]")); + return NULL; +} + +static napi_value throwSyntaxErrorCode(napi_env env, napi_callback_info info) { + NODE_API_CALL(env, + node_api_throw_syntax_error(env, "ERR_TEST_CODE", "SyntaxError [syntax error]")); + return NULL; +} + +static napi_value createError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NODE_API_CALL(env, napi_create_string_utf8( + env, "error", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_error(env, NULL, message, &result)); + return result; +} + +static napi_value createRangeError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NODE_API_CALL(env, napi_create_string_utf8( + env, "range error", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_range_error(env, NULL, message, &result)); + return result; +} + +static napi_value createTypeError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NODE_API_CALL(env, napi_create_string_utf8( + env, "type error", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_type_error(env, NULL, message, &result)); + return result; +} + +static napi_value createSyntaxError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NODE_API_CALL(env, napi_create_string_utf8( + env, "syntax error", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, node_api_create_syntax_error(env, NULL, message, &result)); + return result; +} + +static napi_value createErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NODE_API_CALL(env, napi_create_string_utf8( + env, "Error [error]", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NODE_API_CALL(env, napi_create_error(env, code, message, &result)); + return result; +} + +static napi_value createRangeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "RangeError [range error]", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NODE_API_CALL(env, napi_create_range_error(env, code, message, &result)); + return result; +} + +static napi_value createTypeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "TypeError [type error]", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NODE_API_CALL(env, napi_create_type_error(env, code, message, &result)); + return result; +} + +static napi_value createSyntaxErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "SyntaxError [syntax error]", NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NODE_API_CALL(env, node_api_create_syntax_error(env, code, message, &result)); + return result; +} + +static napi_value throwArbitrary(napi_env env, napi_callback_info info) { + napi_value arbitrary; + size_t argc = 1; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &arbitrary, NULL, NULL)); + NODE_API_CALL(env, napi_throw(env, arbitrary)); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("checkError", checkError), + DECLARE_NODE_API_PROPERTY("throwExistingError", throwExistingError), + DECLARE_NODE_API_PROPERTY("throwError", throwError), + DECLARE_NODE_API_PROPERTY("throwRangeError", throwRangeError), + DECLARE_NODE_API_PROPERTY("throwTypeError", throwTypeError), + DECLARE_NODE_API_PROPERTY("throwSyntaxError", throwSyntaxError), + DECLARE_NODE_API_PROPERTY("throwErrorCode", throwErrorCode), + DECLARE_NODE_API_PROPERTY("throwRangeErrorCode", throwRangeErrorCode), + DECLARE_NODE_API_PROPERTY("throwTypeErrorCode", throwTypeErrorCode), + DECLARE_NODE_API_PROPERTY("throwSyntaxErrorCode", throwSyntaxErrorCode), + DECLARE_NODE_API_PROPERTY("throwArbitrary", throwArbitrary), + DECLARE_NODE_API_PROPERTY("createError", createError), + DECLARE_NODE_API_PROPERTY("createRangeError", createRangeError), + DECLARE_NODE_API_PROPERTY("createTypeError", createTypeError), + DECLARE_NODE_API_PROPERTY("createSyntaxError", createSyntaxError), + DECLARE_NODE_API_PROPERTY("createErrorCode", createErrorCode), + DECLARE_NODE_API_PROPERTY("createRangeErrorCode", createRangeErrorCode), + DECLARE_NODE_API_PROPERTY("createTypeErrorCode", createTypeErrorCode), + DECLARE_NODE_API_PROPERTY("createSyntaxErrorCode", createSyntaxErrorCode), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_exception/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_exception/CMakeLists.txt new file mode 100644 index 00000000..4d8494f9 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_exception/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_exception + SOURCES + test_exception.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_exception/binding.gyp b/Tests/NodeApi/test/js-native-api/test_exception/binding.gyp new file mode 100644 index 00000000..a453505d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_exception/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_exception", + "sources": [ + "test_exception.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_exception/test.js b/Tests/NodeApi/test/js-native-api/test_exception/test.js new file mode 100644 index 00000000..3e070fed --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_exception/test.js @@ -0,0 +1,115 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); +const theError = new Error('Some error'); + +// The test module throws an error during Init, but in order for its exports to +// not be lost, it attaches them to the error's "bindings" property. This way, +// we can make sure that exceptions thrown during the module initialization +// phase are propagated through require() into JavaScript. +// https://github.com/nodejs/node/issues/19437 +const test_exception = (function() { + let resultingException; + try { + require(`./build/${common.buildType}/test_exception`); + } catch (anException) { + resultingException = anException; + } + assert.strictEqual(resultingException.message, 'Error during Init'); + return resultingException.binding; +})(); + +{ + const throwTheError = () => { throw theError; }; + + // Test that the native side successfully captures the exception + let returnedError = test_exception.returnException(throwTheError); + assert.strictEqual(returnedError, theError); + + // Test that the native side passes the exception through + assert.throws( + () => { test_exception.allowException(throwTheError); }, + (err) => err === theError, + ); + + // Test that the exception thrown above was marked as pending + // before it was handled on the JS side + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, true, + 'Exception not pending as expected,' + + ` .wasPending() returned ${exception_pending}`); + + // Test that the native side does not capture a non-existing exception + returnedError = test_exception.returnException(common.mustCall()); + assert.strictEqual(returnedError, undefined, + 'Returned error should be undefined when no exception is' + + ` thrown, but ${returnedError} was passed`); +} + + +{ + const throwTheError = class { constructor() { throw theError; } }; + + // Test that the native side successfully captures the exception + let returnedError = test_exception.constructReturnException(throwTheError); + assert.strictEqual(returnedError, theError); + + // Test that the native side passes the exception through + assert.throws( + () => { test_exception.constructAllowException(throwTheError); }, + (err) => err === theError, + ); + + // Test that the exception thrown above was marked as pending + // before it was handled on the JS side + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, true, + 'Exception not pending as expected,' + + ` .wasPending() returned ${exception_pending}`); + + // Test that the native side does not capture a non-existing exception + returnedError = test_exception.constructReturnException(common.mustCall()); + assert.strictEqual(returnedError, undefined, + 'Returned error should be undefined when no exception is' + + ` thrown, but ${returnedError} was passed`); +} + +{ + // Test that no exception appears that was not thrown by us + let caughtError; + try { + test_exception.allowException(common.mustCall()); + } catch (anError) { + caughtError = anError; + } + assert.strictEqual(caughtError, undefined, + 'No exception originated on the native side, but' + + ` ${caughtError} was passed`); + + // Test that the exception state remains clear when no exception is thrown + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, false, + 'Exception state did not remain clear as expected,' + + ` .wasPending() returned ${exception_pending}`); +} + +{ + // Test that no exception appears that was not thrown by us + let caughtError; + try { + test_exception.constructAllowException(common.mustCall()); + } catch (anError) { + caughtError = anError; + } + assert.strictEqual(caughtError, undefined, + 'No exception originated on the native side, but' + + ` ${caughtError} was passed`); + + // Test that the exception state remains clear when no exception is thrown + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, false, + 'Exception state did not remain clear as expected,' + + ` .wasPending() returned ${exception_pending}`); +} diff --git a/Tests/NodeApi/test/js-native-api/test_exception/testFinalizerException.js b/Tests/NodeApi/test/js-native-api/test_exception/testFinalizerException.js new file mode 100644 index 00000000..dce63624 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_exception/testFinalizerException.js @@ -0,0 +1,31 @@ +'use strict'; +if (process.argv[2] === 'child') { + const common = require('../../common'); + // Trying, catching the exception, and finding the bindings at the `Error`'s + // `binding` property is done intentionally, because we're also testing what + // happens when the add-on entry point throws. See test.js. + try { + require(`./build/${common.buildType}/test_exception`); + } catch (anException) { + anException.binding.createExternal(); + } + + // Collect garbage 10 times. At least one of those should throw the exception + // and cause the whole process to bail with it, its text printed to stderr and + // asserted by the parent process to match expectations. + let gcCount = 10; + (function gcLoop() { + global.gc(); + if (--gcCount > 0) { + setImmediate(() => gcLoop()); + } + })(); +} else { + const assert = require('assert'); + const { spawnSync } = require('child_process'); + const child = spawnSync(process.execPath, [ + '--expose-gc', __filename, 'child', + ]); + assert.strictEqual(child.signal, null); + assert.match(child.stderr.toString(), /Error during Finalize/m); +} \ No newline at end of file diff --git a/Tests/NodeApi/test/js-native-api/test_exception/test_exception.c b/Tests/NodeApi/test/js-native-api/test_exception/test_exception.c new file mode 100644 index 00000000..de1eb42a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_exception/test_exception.c @@ -0,0 +1,116 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static bool exceptionWasPending = false; +static int num = 0x23432; + +static napi_value returnException(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 global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value result; + napi_status status = napi_call_function(env, global, args[0], 0, 0, &result); + if (status == napi_pending_exception) { + napi_value ex; + NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &ex)); + return ex; + } + + return NULL; +} + +static napi_value constructReturnException(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; + napi_status status = napi_new_instance(env, args[0], 0, 0, &result); + if (status == napi_pending_exception) { + napi_value ex; + NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &ex)); + return ex; + } + + return NULL; +} + +static napi_value allowException(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 global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value result; + napi_call_function(env, global, args[0], 0, 0, &result); + // Ignore status and check napi_is_exception_pending() instead. + + NODE_API_CALL(env, napi_is_exception_pending(env, &exceptionWasPending)); + return NULL; +} + +static napi_value constructAllowException(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; + napi_new_instance(env, args[0], 0, 0, &result); + // Ignore status and check napi_is_exception_pending() instead. + + NODE_API_CALL(env, napi_is_exception_pending(env, &exceptionWasPending)); + return NULL; +} + +static napi_value wasPending(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, exceptionWasPending, &result)); + + return result; +} + +static void finalizer(napi_env env, void *data, void *hint) { + NODE_API_CALL_RETURN_VOID(env, + napi_throw_error(env, NULL, "Error during Finalize")); +} + +static napi_value createExternal(napi_env env, napi_callback_info info) { + napi_value external; + + NODE_API_CALL(env, + napi_create_external(env, &num, finalizer, NULL, &external)); + + return external; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("returnException", returnException), + DECLARE_NODE_API_PROPERTY("allowException", allowException), + DECLARE_NODE_API_PROPERTY("constructReturnException", constructReturnException), + DECLARE_NODE_API_PROPERTY("constructAllowException", constructAllowException), + DECLARE_NODE_API_PROPERTY("wasPending", wasPending), + DECLARE_NODE_API_PROPERTY("createExternal", createExternal), + }; + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + napi_value error, code, message; + NODE_API_CALL(env, napi_create_string_utf8(env, "Error during Init", + NAPI_AUTO_LENGTH, &message)); + NODE_API_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &code)); + NODE_API_CALL(env, napi_create_error(env, code, message, &error)); + NODE_API_CALL(env, napi_set_named_property(env, error, "binding", exports)); + NODE_API_CALL(env, napi_throw(env, error)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_finalizer/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_finalizer/CMakeLists.txt new file mode 100644 index 00000000..ce560dad --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_finalizer/CMakeLists.txt @@ -0,0 +1,7 @@ +add_node_api_module(test_finalizer + SOURCES + test_finalizer.c + DEFINES + NAPI_EXPERIMENTAL + NODE_API_EXPERIMENTAL_NO_WARNING +) diff --git a/Tests/NodeApi/test/js-native-api/test_finalizer/binding.gyp b/Tests/NodeApi/test/js-native-api/test_finalizer/binding.gyp new file mode 100644 index 00000000..8553fd2d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_finalizer/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_finalizer", + "defines": [ "NAPI_EXPERIMENTAL" ], + "sources": [ + "test_finalizer.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_finalizer/test.js b/Tests/NodeApi/test/js-native-api/test_finalizer/test.js new file mode 100644 index 00000000..3edf53ce --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_finalizer/test.js @@ -0,0 +1,45 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const test_finalizer = require(`./build/${common.buildType}/test_finalizer`); +const assert = require('assert'); + +const { gcUntil } = require('../../common/gc'); + +// The goal of this test is to show that we can run "pure" finalizers in the +// current JS loop tick. Thus, we do not use gcUntil function works +// asynchronously using micro tasks. +// We use IIFE for the obj scope instead of {} to be compatible with +// non-V8 JS engines that do not support scoped variables. +(() => { + const obj = {}; + test_finalizer.addFinalizer(obj); +})(); + +for (let i = 0; i < 10; ++i) { + global.gc(); + if (test_finalizer.getFinalizerCallCount() === 1) { + break; + } +} + +assert.strictEqual(test_finalizer.getFinalizerCallCount(), 1); + +// The finalizer that access JS cannot run synchronously. They are run in the +// next JS loop tick. Thus, we must use gcUntil. +async function runAsyncTests() { + // We do not use common.mustCall() because we want to see the finalizer + // called in response to GC and not as a part of env destruction. + let js_is_called = false; + // We use IIFE for the obj scope instead of {} to be compatible with + // non-V8 JS engines that do not support scoped variables. + (() => { + const obj = {}; + test_finalizer.addFinalizerWithJS(obj, () => { js_is_called = true; }); + })(); + await gcUntil('ensure JS finalizer called', + () => (test_finalizer.getFinalizerCallCount() === 2)); + assert(js_is_called); +} +runAsyncTests(); diff --git a/Tests/NodeApi/test/js-native-api/test_finalizer/test_fatal_finalize.js b/Tests/NodeApi/test/js-native-api/test_finalizer/test_fatal_finalize.js new file mode 100644 index 00000000..6b725414 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_finalizer/test_fatal_finalize.js @@ -0,0 +1,35 @@ +"use strict"; +const common = require("../../common"); + +if (process.argv[2] === "child") { + const test_finalizer = require(`./build/${common.buildType}/test_finalizer`); + + (() => { + const obj = {}; + test_finalizer.addFinalizerFailOnJS(obj); + })(); + + // Collect garbage 10 times. At least one of those should throw the exception + // and cause the whole process to bail with it, its text printed to stderr and + // asserted by the parent process to match expectations. + let gcCount = 10; + (function gcLoop() { + global.gc(); + if (--gcCount > 0) { + setImmediate(() => gcLoop()); + } + })(); +} else { + const assert = require("assert"); + const { spawnSync } = require("child_process"); + const child = spawnSync(process.execPath, [ + "--expose-gc", + __filename, + "child", + ]); + assert(common.nodeProcessAborted(child.status, child.signal)); + assert.match( + child.stderr.toString(), + /Finalizer is calling a function that may affect GC state/ + ); +} diff --git a/Tests/NodeApi/test/js-native-api/test_finalizer/test_finalizer.c b/Tests/NodeApi/test/js-native-api/test_finalizer/test_finalizer.c new file mode 100644 index 00000000..0d829eee --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_finalizer/test_finalizer.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +typedef struct { + int32_t finalize_count; + napi_ref js_func; +} FinalizerData; + +static void finalizerOnlyCallback(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) { + FinalizerData* data = (FinalizerData*)finalize_data; + int32_t count = ++data->finalize_count; + + // It is safe to access instance data + NODE_API_BASIC_CALL_RETURN_VOID(env, + napi_get_instance_data(env, (void**)&data)); + NODE_API_BASIC_ASSERT_RETURN_VOID(count == data->finalize_count, + "Expected to be the same FinalizerData"); +} + +static void finalizerCallingJSCallback(napi_env env, + void* finalize_data, + void* finalize_hint) { + napi_value js_func, undefined; + FinalizerData* data = (FinalizerData*)finalize_data; + NODE_API_CALL_RETURN_VOID( + env, napi_get_reference_value(env, data->js_func, &js_func)); + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID( + env, napi_call_function(env, undefined, js_func, 0, NULL, NULL)); + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_func)); + data->js_func = NULL; + ++data->finalize_count; +} + +// Schedule async finalizer to run JavaScript-touching code. +static void finalizerWithJSCallback(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) { + NODE_API_BASIC_CALL_RETURN_VOID( + env, + node_api_post_finalizer( + env, finalizerCallingJSCallback, finalize_data, finalize_hint)); +} + +static void finalizerWithFailedJSCallback(node_api_basic_env basic_env, + void* finalize_data, + void* finalize_hint) { + // Intentionally cast to a napi_env to test the fatal failure. + napi_env env = (napi_env)basic_env; + napi_value obj; + FinalizerData* data = (FinalizerData*)finalize_data; + ++data->finalize_count; + NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &obj)); +} + +static napi_value addFinalizer(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1] = {0}; + FinalizerData* data; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL(env, + napi_add_finalizer( + env, argv[0], data, finalizerOnlyCallback, NULL, NULL)); + return NULL; +} + +// This finalizer is going to call JavaScript from finalizer and succeed. +static napi_value addFinalizerWithJS(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {0}; + napi_valuetype arg_type; + FinalizerData* data; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL(env, napi_typeof(env, argv[1], &arg_type)); + NODE_API_ASSERT( + env, arg_type == napi_function, "Expected function as the second arg"); + NODE_API_CALL(env, napi_create_reference(env, argv[1], 1, &data->js_func)); + NODE_API_CALL(env, + napi_add_finalizer( + env, argv[0], data, finalizerWithJSCallback, NULL, NULL)); + return NULL; +} + +// This finalizer is going to call JavaScript from finalizer and fail. +static napi_value addFinalizerFailOnJS(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1] = {0}; + FinalizerData* data; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL( + env, + napi_add_finalizer( + env, argv[0], data, finalizerWithFailedJSCallback, NULL, NULL)); + return NULL; +} + +static napi_value getFinalizerCallCount(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + FinalizerData* data; + napi_value result; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL(env, napi_create_int32(env, data->finalize_count, &result)); + return result; +} + +static void finalizeData(napi_env env, void* data, void* hint) { + free(data); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + FinalizerData* data = (FinalizerData*)malloc(sizeof(FinalizerData)); + NODE_API_ASSERT(env, data != NULL, "Failed to allocate memory"); + memset(data, 0, sizeof(FinalizerData)); + NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, NULL)); + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("addFinalizer", addFinalizer), + DECLARE_NODE_API_PROPERTY("addFinalizerWithJS", addFinalizerWithJS), + DECLARE_NODE_API_PROPERTY("addFinalizerFailOnJS", addFinalizerFailOnJS), + DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", + getFinalizerCallCount)}; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_function/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_function/CMakeLists.txt new file mode 100644 index 00000000..ee290680 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_function/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_function + SOURCES + test_function.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_function/binding.gyp b/Tests/NodeApi/test/js-native-api/test_function/binding.gyp new file mode 100644 index 00000000..7cd97f9d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_function/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_function", + "sources": [ + "test_function.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_function/test.js b/Tests/NodeApi/test/js-native-api/test_function/test.js new file mode 100644 index 00000000..4899fe33 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_function/test.js @@ -0,0 +1,52 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for function +const test_function = require(`./build/${common.buildType}/test_function`); + +function func1() { + return 1; +} +assert.strictEqual(test_function.TestCall(func1), 1); + +function func2() { + console.log('hello world!'); + return null; +} +assert.strictEqual(test_function.TestCall(func2), null); + +function func3(input) { + return input + 1; +} +assert.strictEqual(test_function.TestCall(func3, 1), 2); + +function func4(input) { + return func3(input); +} +assert.strictEqual(test_function.TestCall(func4, 1), 2); + +assert.strictEqual(test_function.TestName.name, 'Name'); +assert.strictEqual(test_function.TestNameShort.name, 'Name_'); + +let tracked_function = test_function.MakeTrackedFunction(common.mustCall()); +assert(!!tracked_function); +tracked_function = null; +global.gc(); + +assert.deepStrictEqual(test_function.TestCreateFunctionParameters(), { + envIsNull: 'Invalid argument', + nameIsNull: 'napi_ok', + cbIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}); + +assert.throws( + () => test_function.TestBadReturnExceptionPending(), + { + code: 'throwing exception', + name: 'Error', + }, +); diff --git a/Tests/NodeApi/test/js-native-api/test_function/test_function.c b/Tests/NodeApi/test/js-native-api/test_function/test_function.c new file mode 100644 index 00000000..be660034 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_function/test_function.c @@ -0,0 +1,204 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestCreateFunctionParameters(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value result, return_value; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + status = napi_create_function(NULL, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + &result); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + status); + + napi_create_function(env, + NULL, + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + &result); + + add_last_status(env, "nameIsNull", return_value); + + napi_create_function(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + NULL, + NULL, + &result); + + add_last_status(env, "cbIsNull", return_value); + + napi_create_function(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, + NULL, + NULL); + + add_last_status(env, "resultIsNull", return_value); + + return return_value; +} + +static napi_value TestCallFunction(napi_env env, napi_callback_info info) { + size_t argc = 10; + napi_value args[10]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc > 0, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_function, + "Wrong type of arguments. Expects a function as first argument."); + + napi_value* argv = args + 1; + argc = argc - 1; + + napi_value global; + NODE_API_CALL(env, napi_get_global(env, &global)); + + napi_value result; + NODE_API_CALL(env, napi_call_function(env, global, args[0], argc, argv, &result)); + + return result; +} + +static napi_value TestFunctionName(napi_env env, napi_callback_info info) { + return NULL; +} + +static void finalize_function(napi_env env, void* data, void* hint) { + napi_ref ref = data; + + // Retrieve the JavaScript undefined value. + napi_value undefined; + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + + // Retrieve the JavaScript function we must call. + napi_value js_function; + NODE_API_CALL_RETURN_VOID(env, napi_get_reference_value(env, ref, &js_function)); + + // Call the JavaScript function to indicate that the generated JavaScript + // function is about to be gc-ed. + NODE_API_CALL_RETURN_VOID(env, + napi_call_function(env, undefined, js_function, 0, NULL, NULL)); + + // Destroy the persistent reference to the function we just called so as to + // properly clean up. + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, ref)); +} + +static napi_value MakeTrackedFunction(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_finalize_cb; + napi_valuetype arg_type; + + // Retrieve and validate from the arguments the function we will use to + // indicate to JavaScript that the function we are about to create is about to + // be gc-ed. + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, &js_finalize_cb, NULL, NULL)); + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + NODE_API_CALL(env, napi_typeof(env, js_finalize_cb, &arg_type)); + NODE_API_ASSERT(env, arg_type == napi_function, "Argument must be a function"); + + // Dynamically create a function. + napi_value result; + NODE_API_CALL(env, + napi_create_function( + env, "TrackedFunction", NAPI_AUTO_LENGTH, TestFunctionName, NULL, + &result)); + + // Create a strong reference to the function we will call when the tracked + // function is about to be gc-ed. + napi_ref js_finalize_cb_ref; + NODE_API_CALL(env, + napi_create_reference(env, js_finalize_cb, 1, &js_finalize_cb_ref)); + + // Attach a finalizer to the dynamically created function and pass it the + // strong reference we created in the previous step. + NODE_API_CALL(env, + napi_wrap( + env, result, js_finalize_cb_ref, finalize_function, NULL, NULL)); + + return result; +} + +static napi_value TestBadReturnExceptionPending(napi_env env, napi_callback_info info) { + napi_throw_error(env, "throwing exception", "throwing exception"); + + // addons should only ever return a valid napi_value even if an + // exception occurs, but we have seen that the C++ wrapper + // with exceptions enabled sometimes returns an invalid value + // when an exception is thrown. Test that we ignore the return + // value then an exception is pending. We use 0xFFFFFFFF as a value + // that should never be a valid napi_value and node seems to + // crash if it is not ignored indicating that it is indeed invalid. + return (napi_value)(0xFFFFFFFFF); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value fn1; + NODE_API_CALL(env, napi_create_function( + env, NULL, NAPI_AUTO_LENGTH, TestCallFunction, NULL, &fn1)); + + napi_value fn2; + NODE_API_CALL(env, napi_create_function( + env, "Name", NAPI_AUTO_LENGTH, TestFunctionName, NULL, &fn2)); + + napi_value fn3; + NODE_API_CALL(env, napi_create_function( + env, "Name_extra", 5, TestFunctionName, NULL, &fn3)); + + napi_value fn4; + NODE_API_CALL(env, + napi_create_function( + env, "MakeTrackedFunction", NAPI_AUTO_LENGTH, MakeTrackedFunction, + NULL, &fn4)); + + napi_value fn5; + NODE_API_CALL(env, + napi_create_function( + env, "TestCreateFunctionParameters", NAPI_AUTO_LENGTH, + TestCreateFunctionParameters, NULL, &fn5)); + + napi_value fn6; + NODE_API_CALL(env, + napi_create_function( + env, "TestBadReturnExceptionPending", NAPI_AUTO_LENGTH, + TestBadReturnExceptionPending, NULL, &fn6)); + + NODE_API_CALL(env, napi_set_named_property(env, exports, "TestCall", fn1)); + NODE_API_CALL(env, napi_set_named_property(env, exports, "TestName", fn2)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "TestNameShort", fn3)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "MakeTrackedFunction", fn4)); + + NODE_API_CALL(env, + napi_set_named_property( + env, exports, "TestCreateFunctionParameters", fn5)); + + NODE_API_CALL(env, + napi_set_named_property( + env, exports, "TestBadReturnExceptionPending", fn6)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_general/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_general/CMakeLists.txt new file mode 100644 index 00000000..3b3532fd --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_general + SOURCES + test_general.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_general/binding.gyp b/Tests/NodeApi/test/js-native-api/test_general/binding.gyp new file mode 100644 index 00000000..71a4fb6b --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_general", + "sources": [ + "test_general.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_general/test.js b/Tests/NodeApi/test/js-native-api/test_general/test.js new file mode 100644 index 00000000..3bf87a55 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/test.js @@ -0,0 +1,97 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const test_general = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +const val1 = '1'; +const val2 = 1; +const val3 = 1; + +class BaseClass { +} + +class ExtendedClass extends BaseClass { +} + +const baseObject = new BaseClass(); +const extendedObject = new ExtendedClass(); + +// Test 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)); + +// Test 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)); + +// Test version management functions +assert.strictEqual(test_general.testGetVersion(), 8); + +[ + 123, + 'test string', + function() {}, + new Object(), + true, + undefined, + Symbol(), +].forEach((val) => { + assert.strictEqual(test_general.testNapiTypeof(val), typeof val); +}); + +// Since typeof in js return object need to validate specific case +// for null +assert.strictEqual(test_general.testNapiTypeof(null), 'null'); + +// Assert that wrapping 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); + +// Ensure that wrapping, removing the wrap, and then wrapping again works. +const y = {}; +test_general.wrap(y); +test_general.removeWrap(y); +// Wrapping twice succeeds if a remove_wrap() separates the instances +test_general.wrap(y); +// Clean up here, otherwise derefItemWasCalled() will be polluted. +test_general.removeWrap(y); + +// Test napi_adjust_external_memory +// TODO: (vmoroz) Hermes does not implement that API. +// const adjustedValue = test_general.testAdjustExternalMemory(); +// assert.strictEqual(typeof adjustedValue, 'number'); +// assert(adjustedValue > 0); + +async function runGCTests() { + // Ensure that garbage collecting an object with a wrapped native item results + // in the finalize callback being called. + // TODO: (vmoroz) Restore after Hermes GC is fixed. + // assert.strictEqual(test_general.derefItemWasCalled(), false); + // (() => test_general.wrap({}))(); + // await common.gcUntil('deref_item() was called upon garbage collecting a ' + + // 'wrapped object.', + // () => test_general.derefItemWasCalled()); + + // Ensure that removing a wrap and garbage collecting does not fire the + // finalize callback. + let z = {}; + test_general.testFinalizeWrap(z); + test_general.removeWrap(z); + z = null; + await common.gcUntil( + 'finalize callback was not called upon garbage collection.', + () => (!test_general.finalizeWasCalled())); +} +runGCTests(); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testEnvCleanup.js b/Tests/NodeApi/test/js-native-api/test_general/testEnvCleanup.js new file mode 100644 index 00000000..ce59768b --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testEnvCleanup.js @@ -0,0 +1,57 @@ +'use strict'; + +if (process.argv[2] === 'child') { + const common = require('../../common'); + const test_general = require(`./build/${common.buildType}/test_general`); + + // The second argument to `envCleanupWrap()` is an index into the global + // static string array named `env_cleanup_finalizer_messages` on the native + // side. A reverse mapping is reproduced here for clarity. + const finalizerMessages = { + 'simple wrap': 0, + 'wrap, removeWrap': 1, + 'first wrap': 2, + 'second wrap': 3, + }; + + // We attach the three objects we will test to `module.exports` to ensure they + // will not be garbage-collected before the process exits. + + // Make sure the finalizer for a simple wrap will be called at env cleanup. + module.exports['simple wrap'] = + test_general.envCleanupWrap({}, finalizerMessages['simple wrap']); + + // Make sure that a removed wrap does not result in a call to its finalizer at + // env cleanup. + module.exports['wrap, removeWrap'] = + test_general.envCleanupWrap({}, finalizerMessages['wrap, removeWrap']); + test_general.removeWrap(module.exports['wrap, removeWrap']); + + // Make sure that only the latest attached version of a re-wrapped item's + // finalizer gets called at env cleanup. + module.exports['first wrap'] = + test_general.envCleanupWrap({}, finalizerMessages['first wrap']); + test_general.removeWrap(module.exports['first wrap']); + test_general.envCleanupWrap(module.exports['first wrap'], + finalizerMessages['second wrap']); +} else { + const assert = require('assert'); + const { spawnSync } = require('child_process'); + + const child = spawnSync(process.execPath, [__filename, 'child'], { + stdio: [ process.stdin, 'pipe', process.stderr ], + }); + + // Grab the child's output and construct an object whose keys are the rows of + // the output and whose values are `true`, so we can compare the output while + // ignoring the order in which the lines of it were produced. + assert.deepStrictEqual( + child.stdout.toString().split(/\r\n|\r|\n/g).reduce((obj, item) => + Object.assign(obj, item ? { [item]: true } : {}), {}), { + 'finalize at env cleanup for simple wrap': true, + 'finalize at env cleanup for second wrap': true, + }); + + // Ensure that the child exited successfully. + assert.strictEqual(child.status, 0); +} diff --git a/Tests/NodeApi/test/js-native-api/test_general/testFinalizer.js b/Tests/NodeApi/test/js-native-api/test_general/testFinalizer.js new file mode 100644 index 00000000..3eefe142 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testFinalizer.js @@ -0,0 +1,38 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const test_general = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +(function() { + let finalized = {}; + const callback = common.mustCall(2); + + // Add two items to be finalized and ensure the callback is called for each. + test_general.addFinalizerOnly(finalized, callback); + test_general.addFinalizerOnly(finalized, callback); + + // Ensure attached items cannot be retrieved. + assert.throws(() => test_general.unwrap(finalized), + { name: 'Error', message: 'Invalid argument' }); + + // Ensure attached items cannot be removed. + assert.throws(() => test_general.removeWrap(finalized), + { name: 'Error', message: 'Invalid argument' }); +})(); +global.gc(); + +// Add an item to an object that is already wrapped, and ensure that its +// finalizer as well as the wrap finalizer gets called. +async function testFinalizeAndWrap() { + assert.strictEqual(test_general.derefItemWasCalled(), false); + (function() { + let finalizeAndWrap = {}; + test_general.wrap(finalizeAndWrap); + test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall()); + })(); + await common.gcUntil('test finalize and wrap', + () => test_general.derefItemWasCalled()); +} +testFinalizeAndWrap(); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testGlobals.js b/Tests/NodeApi/test/js-native-api/test_general/testGlobals.js new file mode 100644 index 00000000..34188e08 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testGlobals.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +const test_globals = require(`./build/${common.buildType}/test_general`); + +assert.strictEqual(test_globals.getUndefined(), undefined); +assert.strictEqual(test_globals.getNull(), null); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testInstanceOf.js b/Tests/NodeApi/test/js-native-api/test_general/testInstanceOf.js new file mode 100644 index 00000000..c9b98fa8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testInstanceOf.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Addon is referenced through the eval expression in testFile +const addon = require(`./build/${common.buildType}/test_general`); + +// We can only perform this test if we have a working Symbol.hasInstance +if (typeof Symbol !== 'undefined' && 'hasInstance' in Symbol && + typeof Symbol.hasInstance === 'symbol') { + + function compareToNative(theObject, theConstructor) { + assert.strictEqual( + addon.doInstanceOf(theObject, theConstructor), + (theObject instanceof theConstructor), + ); + } + + function MyClass() {} + Object.defineProperty(MyClass, Symbol.hasInstance, { + value: function(candidate) { + return 'mark' in candidate; + }, + }); + + function MySubClass() {} + MySubClass.prototype = new MyClass(); + + let x = new MySubClass(); + let y = new MySubClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); + + x = new MyClass(); + y = new MyClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); +} diff --git a/Tests/NodeApi/test/js-native-api/test_general/testNapiRun.js b/Tests/NodeApi/test/js-native-api/test_general/testNapiRun.js new file mode 100644 index 00000000..6d4f4662 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testNapiRun.js @@ -0,0 +1,14 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); + +// `addon` is referenced through the eval expression in testFile +const addon = require(`./build/${common.buildType}/test_general`); + +const testCase = '(41.92 + 0.08);'; +const expected = 42; +const actual = addon.testNapiRun(testCase); + +assert.strictEqual(actual, expected); +assert.throws(() => addon.testNapiRun({ abc: 'def' }), /string was expected/); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testNapiStatus.js b/Tests/NodeApi/test/js-native-api/test_general/testNapiStatus.js new file mode 100644 index 00000000..5ad97a34 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testNapiStatus.js @@ -0,0 +1,8 @@ +'use strict'; + +const common = require('../../common'); +const addon = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +addon.createNapiError(); +assert(addon.testNapiErrorCleanup(), 'napi_status cleaned up for second call'); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof.js b/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof.js new file mode 100644 index 00000000..0b476e1e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof.js @@ -0,0 +1,121 @@ +// Copyright 2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +const common = require('../../common'); +const addon = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +// The following assert functions are referenced by v8's unit tests +// See for instance deps/v8/test/mjsunit/instanceof.js +// eslint-disable-next-line no-unused-vars +function assertTrue(assertion) { + return assert.strictEqual(assertion, true); +} + +// eslint-disable-next-line no-unused-vars +function assertFalse(assertion) { + assert.strictEqual(assertion, false); +} + +// eslint-disable-next-line no-unused-vars +function assertEquals(leftHandSide, rightHandSide) { + assert.strictEqual(leftHandSide, rightHandSide); +} + +// eslint-disable-next-line no-unused-vars +function assertThrows(statement) { + assert.throws(function() { + eval(statement); + }, Error); +} + +assertTrue(addon.doInstanceOf({}, Object)); +assertTrue(addon.doInstanceOf([], Object)); + +assertFalse(addon.doInstanceOf({}, Array)); +assertTrue(addon.doInstanceOf([], Array)); + +function TestChains() { + var A = {}; + var B = {}; + var C = {}; + B.__proto__ = A; + C.__proto__ = B; + + function F() { } + F.prototype = A; + assertTrue(addon.doInstanceOf(C, F)); + assertTrue(addon.doInstanceOf(B, F)); + assertFalse(addon.doInstanceOf(A, F)); + + F.prototype = B; + assertTrue(addon.doInstanceOf(C, F)); + assertFalse(addon.doInstanceOf(B, F)); + assertFalse(addon.doInstanceOf(A, F)); + + F.prototype = C; + assertFalse(addon.doInstanceOf(C, F)); + assertFalse(addon.doInstanceOf(B, F)); + assertFalse(addon.doInstanceOf(A, F)); +} + +TestChains(); + + +function TestExceptions() { + function F() { } + var items = [ 1, new Number(42), + true, + 'string', new String('hest'), + {}, [], + F, new F(), + Object, String ]; + + var exceptions = 0; + var instanceofs = 0; + + for (var i = 0; i < items.length; i++) { + for (var j = 0; j < items.length; j++) { + try { + if (addon.doInstanceOf(items[i], items[j])) instanceofs++; + } catch (e) { + assertTrue(addon.doInstanceOf(e, TypeError)); + exceptions++; + } + } + } + assertEquals(10, instanceofs); + assertEquals(88, exceptions); + + // Make sure to throw an exception if the function prototype + // isn't a proper JavaScript object. + function G() { } + G.prototype = undefined; + assertThrows("addon.doInstanceOf({}, G)"); +} + +TestExceptions(); diff --git a/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof2.js b/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof2.js new file mode 100644 index 00000000..360b28c8 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/testV8Instanceof2.js @@ -0,0 +1,341 @@ +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +const common = require('../../common'); +const addon = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +function assertTrue(assertion) { + return assert.strictEqual(assertion, true); +} + +function assertEquals(leftHandSide, rightHandSide) { + assert.strictEqual(leftHandSide, rightHandSide); +} + +var except = "exception"; + +var correct_answer_index = 0; +var correct_answers = [ + false, false, true, true, false, false, true, true, + true, false, false, true, true, false, false, true, + false, true, true, false, false, true, true, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, false, true, +except, except, true, false, except, except, true, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, true, false, except, except, + false, true, except, except, false, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, false, true, true, false, + true, true, false, false, false, true, true, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, true, false, +except, except, false, false, except, except, true, false, + false, false, except, except, false, false, except, except, + true, false, except, except, true, false, except, except, + false, true, except, except, false, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, true, true, false, + true, false, false, true, true, true, false, false, + false, true, true, false, false, true, true, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, false, true, +except, except, true, false, except, except, true, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, true, true, false, + true, false, false, true, false, true, true, false, + false, true, true, false, false, true, true, false, + true, true, false, false, false, true, true, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, true, false, +except, except, false, false, except, except, true, false, + false, false, except, except, false, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, false, false, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, false, false, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, true, true, false, false, + true, false, false, true, true, true, false, false, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, true, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, true, true, false, false, + true, false, false, true, true, true, false, false, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, true, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, true, true, false, false, + false, true, true, false, false, false, true, true, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, false, false, +except, except, true, false, except, except, true, true, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, false, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, false, false, true, true, + true, true, false, false, false, false, true, true, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, true, true, +except, except, false, false, except, except, true, true, + false, false, except, except, false, false, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, false, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, true, true, false, false, + false, true, true, false, false, false, true, true, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, false, false, +except, except, true, false, except, except, true, true, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, false, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, false, false, true, true, + true, true, false, false, false, false, true, true, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, true, true, +except, except, false, false, except, except, true, true, + false, false, except, except, false, false, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, false, false, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, false, false, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, false, false, true, true, + true, false, false, true, false, false, true, true, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, false, false, except, except, + true, false, except, except, false, false, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, true, true, false, false, + true, false, false, true, true, true, false, false, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, true, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, + false, false, true, true, true, true, false, false, + true, false, false, true, true, true, false, false, + false, true, true, false, true, true, false, false, + true, true, false, false, true, true, false, false, +except, except, true, true, except, except, true, true, +except, except, false, true, except, except, true, true, +except, except, true, false, except, except, false, false, +except, except, false, false, except, except, false, false, + false, false, except, except, true, true, except, except, + true, false, except, except, true, true, except, except, + false, true, except, except, true, true, except, except, + true, true, except, except, true, true, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except, +except, except, except, except, except, except, except, except]; + +for (var i = 0; i < 256; i++) { + Test(i & 1, i & 2, i & 4, i & 8, i & 0x10, i & 0x20, i & 0x40, i & 0x80); +} + + +function InstanceTest(x, func) { + try { + var answer = addon.doInstanceOf(x, func); + assertEquals(correct_answers[correct_answer_index], answer); + } catch (e) { + assertTrue(/prototype/.test(e)); + assertEquals(correct_answers[correct_answer_index], except); + } + correct_answer_index++; +} + + +function Test(a, b, c, d, e, f, g, h) { + var Foo = function() { } + var Bar = function() { } + + if (c) Foo.prototype = 12; + if (d) Bar.prototype = 13; + var x = a ? new Foo() : new Bar(); + var y = b ? new Foo() : new Bar(); + InstanceTest(x, Foo); + InstanceTest(y, Foo); + InstanceTest(x, Bar); + InstanceTest(y, Bar); + if (e) x.__proto__ = Bar.prototype; + if (f) y.__proto__ = Foo.prototype; + if (g) { + x.__proto__ = y; + } else { + if (h) y.__proto__ = x + } + InstanceTest(x, Foo); + InstanceTest(y, Foo); + InstanceTest(x, Bar); + InstanceTest(y, Bar); +} diff --git a/Tests/NodeApi/test/js-native-api/test_general/test_general.c b/Tests/NodeApi/test/js-native-api/test_general/test_general.c new file mode 100644 index 00000000..2e130af6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_general/test_general.c @@ -0,0 +1,315 @@ +// we define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED here to +// validate that it can be used as a form of test itself. It is +// not related to any of the other tests +// defined in the file +#define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +#include +#include +#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 doInstanceOf(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 instanceof; + NODE_API_CALL(env, napi_instanceof(env, args[0], args[1], &instanceof)); + + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, instanceof, &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)); + + napi_value result = NULL; + if (argument_type == napi_number) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "number", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_string) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "string", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_function) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "function", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_object) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "object", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_boolean) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "boolean", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_undefined) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "undefined", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_symbol) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "symbol", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_null) { + NODE_API_CALL(env, napi_create_string_utf8( + env, "null", NAPI_AUTO_LENGTH, &result)); + } + return result; +} + +static bool deref_item_called = false; +static void deref_item(napi_env env, void* data, void* hint) { + (void) hint; + + NODE_API_ASSERT_RETURN_VOID(env, 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, + napi_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 unwrap(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_unwrap(env, wrapped, &data)); + + return NULL; +} + +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(napi_env 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; +} + +static void finalizer_only_callback(napi_env env, void* data, void* hint) { + napi_ref js_cb_ref = data; + napi_value js_cb, undefined; + NODE_API_CALL_RETURN_VOID(env, napi_get_reference_value(env, js_cb_ref, &js_cb)); + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(env, + napi_call_function(env, undefined, js_cb, 0, NULL, NULL)); + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_cb_ref)); +} + +static napi_value add_finalizer_only(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2]; + napi_ref js_cb_ref; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_create_reference(env, argv[1], 1, &js_cb_ref)); + NODE_API_CALL(env, + napi_add_finalizer( + env, argv[0], js_cb_ref, finalizer_only_callback, NULL, NULL)); + return NULL; +} + +static const char* env_cleanup_finalizer_messages[] = { + "simple wrap", + "wrap, removeWrap", + "first wrap", + "second wrap" +}; + +static void cleanup_env_finalizer(napi_env env, void* data, void* hint) { + (void) env; + (void) hint; + + printf("finalize at env cleanup for %s\n", + env_cleanup_finalizer_messages[(uintptr_t)data]); +} + +static napi_value env_cleanup_wrap(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2]; + uint32_t value; + uintptr_t ptr_value; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + + NODE_API_CALL(env, napi_get_value_uint32(env, argv[1], &value)); + + ptr_value = value; + return wrap_first_arg(env, info, cleanup_env_finalizer, (void*)ptr_value); +} + +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("doInstanceOf", doInstanceOf), + 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("envCleanupWrap", env_cleanup_wrap), + DECLARE_NODE_API_PROPERTY("unwrap", unwrap), + DECLARE_NODE_API_PROPERTY("removeWrap", remove_wrap), + DECLARE_NODE_API_PROPERTY("addFinalizerOnly", add_finalizer_only), + 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 diff --git a/Tests/NodeApi/test/js-native-api/test_handle_scope/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_handle_scope/CMakeLists.txt new file mode 100644 index 00000000..579119a3 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_handle_scope/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_handle_scope + SOURCES + test_handle_scope.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_handle_scope/binding.gyp b/Tests/NodeApi/test/js-native-api/test_handle_scope/binding.gyp new file mode 100644 index 00000000..dd6dd63a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_handle_scope/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_handle_scope", + "sources": [ + "test_handle_scope.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_handle_scope/test.js b/Tests/NodeApi/test/js-native-api/test_handle_scope/test.js new file mode 100644 index 00000000..46aa12f6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_handle_scope/test.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing handle scope api calls +const testHandleScope = + require(`./build/${common.buildType}/test_handle_scope`); + +testHandleScope.NewScope(); + +assert.ok(testHandleScope.NewScopeEscape() instanceof Object); + +testHandleScope.NewScopeEscapeTwice(); + +assert.throws( + () => { + testHandleScope.NewScopeWithException(() => { throw new RangeError(); }); + }, + RangeError); diff --git a/Tests/NodeApi/test/js-native-api/test_handle_scope/test_handle_scope.c b/Tests/NodeApi/test/js-native-api/test_handle_scope/test_handle_scope.c new file mode 100644 index 00000000..7c5eb4a4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_handle_scope/test_handle_scope.c @@ -0,0 +1,86 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +// these tests validate the handle scope functions in the normal +// flow. Forcing gc behavior to fully validate they are doing +// the right right thing would be quite hard so we keep it +// simple for now. + +static napi_value NewScope(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + napi_value output = NULL; + + NODE_API_CALL(env, napi_open_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +static napi_value NewScopeEscape(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + + NODE_API_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + NODE_API_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return escapee; +} + +static napi_value NewScopeEscapeTwice(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + napi_status status; + + NODE_API_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + NODE_API_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + status = napi_escape_handle(env, scope, output, &escapee); + NODE_API_ASSERT(env, status == napi_escape_called_twice, "Escaping twice fails"); + NODE_API_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return NULL; +} + +static napi_value NewScopeWithException(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + size_t argc; + napi_value exception_function; + napi_status status; + napi_value output = NULL; + + NODE_API_CALL(env, napi_open_handle_scope(env, &scope)); + NODE_API_CALL(env, napi_create_object(env, &output)); + + argc = 1; + NODE_API_CALL(env, napi_get_cb_info( + env, info, &argc, &exception_function, NULL, NULL)); + + status = napi_call_function( + env, output, exception_function, 0, NULL, NULL); + NODE_API_ASSERT(env, status == napi_pending_exception, + "Function should have thrown."); + + // Closing a handle scope should still work while an exception is pending. + NODE_API_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("NewScope", NewScope), + DECLARE_NODE_API_PROPERTY("NewScopeEscape", NewScopeEscape), + DECLARE_NODE_API_PROPERTY("NewScopeEscapeTwice", NewScopeEscapeTwice), + DECLARE_NODE_API_PROPERTY("NewScopeWithException", NewScopeWithException), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_instance_data/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_instance_data/CMakeLists.txt new file mode 100644 index 00000000..feda8f59 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_instance_data/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_instance_data + SOURCES + test_instance_data.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_instance_data/binding.gyp b/Tests/NodeApi/test/js-native-api/test_instance_data/binding.gyp new file mode 100644 index 00000000..f1c77b30 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_instance_data/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_instance_data", + "sources": [ + "test_instance_data.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_instance_data/test.js b/Tests/NodeApi/test/js-native-api/test_instance_data/test.js new file mode 100644 index 00000000..043da9f1 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_instance_data/test.js @@ -0,0 +1,41 @@ +'use strict'; +// Test API calls for instance data. + +const common = require('../../common'); +const assert = require('assert'); + +if (module !== require.main) { + // When required as a module, run the tests. + const test_instance_data = + require(`./build/${common.buildType}/test_instance_data`); + + // Print to stdout when the environment deletes the instance data. This output + // is checked by the parent process. + test_instance_data.setPrintOnDelete(); + + // Test that instance data can be accessed from a binding. + assert.strictEqual(test_instance_data.increment(), 42); + + // Test that the instance data can be accessed from a finalizer. + // TODO: (vmoroz) Restore after Hermes fixes GC. + // test_instance_data.objectWithFinalizer(common.mustCall()); + // global.gc(); +} else { + // When launched as a script, run tests in either a child process or in a + // worker thread. + const requireAs = require('../../common/require-as'); + const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] }; + + function checkOutput(child) { + assert.strictEqual(child.status, 0); + assert.strictEqual( + (child.stdout.toString().split(/\r\n?|\n/) || [])[0], + 'deleting addon data'); + } + + // Run tests in a child process. + checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'child')); + + // Run tests in a worker thread in a child process. + checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'worker')); +} diff --git a/Tests/NodeApi/test/js-native-api/test_instance_data/test_instance_data.c b/Tests/NodeApi/test/js-native-api/test_instance_data/test_instance_data.c new file mode 100644 index 00000000..bf354983 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_instance_data/test_instance_data.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +typedef struct { + size_t value; + bool print; + napi_ref js_cb_ref; +} AddonData; + +static napi_value Increment(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_CALL(env, napi_create_uint32(env, ++data->value, &result)); + + return result; +} + +static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { + AddonData* data = raw_data; + if (data->print) { + printf("deleting addon data\n"); + } + if (data->js_cb_ref != NULL) { + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + } + free(data); +} + +static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) { + AddonData* data; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + data->print = true; + + return NULL; +} + +static void TestFinalizer(napi_env env, void* raw_data, void* hint) { + (void) raw_data; + (void) hint; + + AddonData* data; + NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + napi_value js_cb, undefined; + NODE_API_CALL_RETURN_VOID(env, + napi_get_reference_value(env, data->js_cb_ref, &js_cb)); + NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(env, + napi_call_function(env, undefined, js_cb, 0, NULL, NULL)); + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + data->js_cb_ref = NULL; +} + +static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result, js_cb; + size_t argc = 1; + + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); + NODE_API_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL)); + NODE_API_CALL(env, napi_create_object(env, &result)); + NODE_API_CALL(env, + napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL)); + NODE_API_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + AddonData* data = malloc(sizeof(*data)); + data->value = 41; + data->print = false; + data->js_cb_ref = NULL; + + NODE_API_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); + + napi_property_descriptor props[] = { + DECLARE_NODE_API_PROPERTY("increment", Increment), + DECLARE_NODE_API_PROPERTY("setPrintOnDelete", SetPrintOnDelete), + DECLARE_NODE_API_PROPERTY("objectWithFinalizer", ObjectWithFinalizer), + }; + + NODE_API_CALL(env, + napi_define_properties( + env, exports, sizeof(props) / sizeof(*props), props)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_new_target/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_new_target/CMakeLists.txt new file mode 100644 index 00000000..1cee2625 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_new_target/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_new_target + SOURCES + test_new_target.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_new_target/binding.gyp b/Tests/NodeApi/test/js-native-api/test_new_target/binding.gyp new file mode 100644 index 00000000..baa0e3c6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_new_target/binding.gyp @@ -0,0 +1,11 @@ +{ + 'targets': [ + { + 'target_name': 'test_new_target', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ + 'test_new_target.c' + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_new_target/test.js b/Tests/NodeApi/test/js-native-api/test_new_target/test.js new file mode 100644 index 00000000..06d5ef36 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_new_target/test.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const binding = require(`./build/${common.buildType}/test_new_target`); + +class Class extends binding.BaseClass { + constructor() { + super(); + this.method(); + } + method() { + this.ok = true; + } +} + +assert.ok(new Class() instanceof binding.BaseClass); +assert.ok(new Class().ok); +assert.ok(binding.OrdinaryFunction()); +assert.ok( + new binding.Constructor(binding.Constructor) instanceof binding.Constructor); diff --git a/Tests/NodeApi/test/js-native-api/test_new_target/test_new_target.c b/Tests/NodeApi/test/js-native-api/test_new_target/test_new_target.c new file mode 100644 index 00000000..02ceb992 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_new_target/test_new_target.c @@ -0,0 +1,92 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value BaseClass(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + napi_value thisArg; + NODE_API_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &thisArg, NULL)); + napi_value undefined; + NODE_API_CALL(env, napi_get_undefined(env, &undefined)); + + // this !== new.target since we are being invoked through super() + bool result; + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, thisArg, &result)); + NODE_API_ASSERT(env, !result, "this !== new.target"); + + // new.target !== undefined because we should be called as a new expression + NODE_API_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NODE_API_ASSERT(env, !result, "new.target !== undefined"); + + return thisArg; +} + +static napi_value Constructor(napi_env env, napi_callback_info info) { + bool result; + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + size_t argc = 1; + napi_value argv; + napi_value thisArg; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisArg, NULL)); + napi_value undefined; + NODE_API_CALL(env, napi_get_undefined(env, &undefined)); + + // new.target !== undefined because we should be called as a new expression + NODE_API_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NODE_API_ASSERT(env, !result, "new.target !== undefined"); + + // arguments[0] should be Constructor itself (test harness passed it) + NODE_API_CALL(env, napi_strict_equals(env, newTargetArg, argv, &result)); + NODE_API_ASSERT(env, result, "new.target === Constructor"); + + return thisArg; +} + +static napi_value OrdinaryFunction(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NODE_API_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + + NODE_API_ASSERT(env, newTargetArg == NULL, "newTargetArg == NULL"); + + napi_value _true; + NODE_API_CALL(env, napi_get_boolean(env, true, &_true)); + return _true; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value baseClass, constructor; + NODE_API_CALL( + env, + napi_define_class( + env, + "BaseClass", + NAPI_AUTO_LENGTH, + BaseClass, + NULL, + 0, + NULL, + &baseClass)); + NODE_API_CALL( + env, + napi_define_class( + env, + "Constructor", + NAPI_AUTO_LENGTH, + Constructor, + NULL, + 0, + NULL, + &constructor)); + const napi_property_descriptor desc[] = { + DECLARE_NODE_API_PROPERTY_VALUE("BaseClass", baseClass), + DECLARE_NODE_API_PROPERTY("OrdinaryFunction", OrdinaryFunction), + DECLARE_NODE_API_PROPERTY_VALUE("Constructor", constructor)}; + NODE_API_CALL(env, napi_define_properties(env, exports, 3, desc)); + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_number/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_number/CMakeLists.txt new file mode 100644 index 00000000..87946947 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/CMakeLists.txt @@ -0,0 +1,5 @@ +add_node_api_module(test_number + SOURCES + test_number.c + test_null.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_number/binding.gyp b/Tests/NodeApi/test/js-native-api/test_number/binding.gyp new file mode 100644 index 00000000..31707404 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_number", + "sources": [ + "test_number.c", + "test_null.c", + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_number/test.js b/Tests/NodeApi/test/js-native-api/test_number/test.js new file mode 100644 index 00000000..6c0f0f3e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/test.js @@ -0,0 +1,134 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_number = require(`./build/${common.buildType}/test_number`); + + +// Testing api calls for number +function testNumber(num) { + assert.strictEqual(num, test_number.Test(num)); +} + +testNumber(0); +testNumber(-0); +testNumber(1); +testNumber(-1); +testNumber(100); +testNumber(2121); +testNumber(-1233); +testNumber(986583); +testNumber(-976675); + +/* eslint-disable no-loss-of-precision */ +testNumber( + 98765432213456789876546896323445679887645323232436587988766545658); +testNumber( + -4350987086545760976737453646576078997096876957864353245245769809); +/* eslint-enable no-loss-of-precision */ +testNumber(Number.MIN_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER + 10); + +testNumber(Number.MIN_VALUE); +testNumber(Number.MAX_VALUE); +testNumber(Number.MAX_VALUE + 10); + +testNumber(Number.POSITIVE_INFINITY); +testNumber(Number.NEGATIVE_INFINITY); +testNumber(Number.NaN); + +function testUint32(input, expected = input) { + assert.strictEqual(expected, test_number.TestUint32Truncation(input)); +} + +// Test zero +testUint32(0.0, 0); +testUint32(-0.0, 0); + +// Test overflow scenarios +testUint32(4294967295); +testUint32(4294967296, 0); +testUint32(4294967297, 1); +testUint32(17 * 4294967296 + 1, 1); +testUint32(-1, 0xffffffff); + +// Validate documented behavior when value is retrieved as 32-bit integer with +// `napi_get_value_int32` +function testInt32(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt32Truncation(input)); +} + +// Test zero +testInt32(0.0, 0); +testInt32(-0.0, 0); + +// Test min/max int32 range +testInt32(-Math.pow(2, 31)); +testInt32(Math.pow(2, 31) - 1); + +// Test overflow scenarios +testInt32(4294967297, 1); +testInt32(4294967296, 0); +testInt32(4294967295, -1); +testInt32(4294967296 * 5 + 3, 3); + +// Test min/max safe integer range +testInt32(Number.MIN_SAFE_INTEGER, 1); +testInt32(Number.MAX_SAFE_INTEGER, -1); + +// Test within int64_t range (with precision loss) +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9) + 1), 1024); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9) + 1), -1024); + +// Test min/max double value +testInt32(-Number.MIN_VALUE, 0); +testInt32(Number.MIN_VALUE, 0); +testInt32(-Number.MAX_VALUE, 0); +testInt32(Number.MAX_VALUE, 0); + +// Test outside int64_t range +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9)), 0); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9)), 0); + +// Test non-finite numbers +testInt32(Number.POSITIVE_INFINITY, 0); +testInt32(Number.NEGATIVE_INFINITY, 0); +testInt32(Number.NaN, 0); + +// Validate documented behavior when value is retrieved as 64-bit integer with +// `napi_get_value_int64` +function testInt64(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt64Truncation(input)); +} + +// Both V8 and ChakraCore return a sentinel value of `0x8000000000000000` when +// the conversion goes out of range, but V8 treats it as unsigned in some cases. +const RANGEERROR_POSITIVE = Math.pow(2, 63); +const RANGEERROR_NEGATIVE = -Math.pow(2, 63); + +// Test zero +testInt64(0.0, 0); +testInt64(-0.0, 0); + +// Test min/max safe integer range +testInt64(Number.MIN_SAFE_INTEGER); +testInt64(Number.MAX_SAFE_INTEGER); + +// Test within int64_t range (with precision loss) +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9) + 1)); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9) + 1)); + +// Test min/max double value +testInt64(-Number.MIN_VALUE, 0); +testInt64(Number.MIN_VALUE, 0); +testInt64(-Number.MAX_VALUE, RANGEERROR_NEGATIVE); +testInt64(Number.MAX_VALUE, RANGEERROR_POSITIVE); + +// Test outside int64_t range +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9)), RANGEERROR_NEGATIVE); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9)), RANGEERROR_POSITIVE); + +// Test non-finite numbers +testInt64(Number.POSITIVE_INFINITY, 0); +testInt64(Number.NEGATIVE_INFINITY, 0); +testInt64(Number.NaN, 0); diff --git a/Tests/NodeApi/test/js-native-api/test_number/test_null.c b/Tests/NodeApi/test/js-native-api/test_number/test_null.c new file mode 100644 index 00000000..20d479c9 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/test_null.c @@ -0,0 +1,77 @@ +#include + +#include "../common.h" + +// Unifies the way the macros declare values. +typedef double double_t; + +#define BINDING_FOR_CREATE(initial_capital, lowercase) \ + static napi_value Create##initial_capital(napi_env env, \ + napi_callback_info info) { \ + napi_value return_value, call_result; \ + lowercase##_t value = 42; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + add_returned_status(env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + napi_create_##lowercase(NULL, value, &call_result)); \ + napi_create_##lowercase(env, value, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + return return_value; \ + } + +#define BINDING_FOR_GET_VALUE(initial_capital, lowercase) \ + static napi_value GetValue##initial_capital(napi_env env, \ + napi_callback_info info) { \ + napi_value return_value, call_result; \ + lowercase##_t value = 42; \ + NODE_API_CALL(env, napi_create_object(env, &return_value)); \ + NODE_API_CALL(env, napi_create_##lowercase(env, value, &call_result)); \ + add_returned_status( \ + env, \ + "envIsNull", \ + return_value, \ + "Invalid argument", \ + napi_invalid_arg, \ + napi_get_value_##lowercase(NULL, call_result, &value)); \ + napi_get_value_##lowercase(env, NULL, &value); \ + add_last_status(env, "valueIsNull", return_value); \ + napi_get_value_##lowercase(env, call_result, NULL); \ + add_last_status(env, "resultIsNull", return_value); \ + return return_value; \ + } + +BINDING_FOR_CREATE(Double, double) +BINDING_FOR_CREATE(Int32, int32) +BINDING_FOR_CREATE(Uint32, uint32) +BINDING_FOR_CREATE(Int64, int64) +BINDING_FOR_GET_VALUE(Double, double) +BINDING_FOR_GET_VALUE(Int32, int32) +BINDING_FOR_GET_VALUE(Uint32, uint32) +BINDING_FOR_GET_VALUE(Int64, int64) + +void init_test_null(napi_env env, napi_value exports) { + const napi_property_descriptor test_null_props[] = { + DECLARE_NODE_API_PROPERTY("createDouble", CreateDouble), + DECLARE_NODE_API_PROPERTY("createInt32", CreateInt32), + DECLARE_NODE_API_PROPERTY("createUint32", CreateUint32), + DECLARE_NODE_API_PROPERTY("createInt64", CreateInt64), + DECLARE_NODE_API_PROPERTY("getValueDouble", GetValueDouble), + DECLARE_NODE_API_PROPERTY("getValueInt32", GetValueInt32), + DECLARE_NODE_API_PROPERTY("getValueUint32", GetValueUint32), + DECLARE_NODE_API_PROPERTY("getValueInt64", GetValueInt64), + }; + napi_value test_null; + + NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &test_null)); + NODE_API_CALL_RETURN_VOID( + env, + napi_define_properties(env, + test_null, + sizeof(test_null_props) / sizeof(*test_null_props), + test_null_props)); + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "testNull", test_null)); +} diff --git a/Tests/NodeApi/test/js-native-api/test_number/test_null.h b/Tests/NodeApi/test/js-native-api/test_number/test_null.h new file mode 100644 index 00000000..695d8971 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_NUMBER_TEST_NULL_H_ diff --git a/Tests/NodeApi/test/js-native-api/test_number/test_null.js b/Tests/NodeApi/test/js-native-api/test_number/test_null.js new file mode 100644 index 00000000..c09801ac --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/test_null.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const { testNull } = require(`./build/${common.buildType}/test_number`); + +const expectedCreateResult = { + envIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}; +const expectedGetValueResult = { + envIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', +}; +[ 'Double', 'Int32', 'Uint32', 'Int64' ].forEach((typeName) => { + assert.deepStrictEqual(testNull['create' + typeName](), expectedCreateResult); + assert.deepStrictEqual(testNull['getValue' + typeName](), expectedGetValueResult); +}); diff --git a/Tests/NodeApi/test/js-native-api/test_number/test_number.c b/Tests/NodeApi/test/js-native-api/test_number/test_number.c new file mode 100644 index 00000000..3b3ba29f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_number/test_number.c @@ -0,0 +1,110 @@ +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +static napi_value Test(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + double input; + NODE_API_CALL(env, napi_get_value_double(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_double(env, input, &output)); + + return output; +} + +static napi_value TestUint32Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + uint32_t input; + NODE_API_CALL(env, napi_get_value_uint32(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_uint32(env, input, &output)); + + return output; +} + +static napi_value TestInt32Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int32_t input; + NODE_API_CALL(env, napi_get_value_int32(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_int32(env, input, &output)); + + return output; +} + +static napi_value TestInt64Truncation(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int64_t input; + NODE_API_CALL(env, napi_get_value_int64(env, args[0], &input)); + + napi_value output; + NODE_API_CALL(env, napi_create_int64(env, input, &output)); + + return output; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("Test", Test), + DECLARE_NODE_API_PROPERTY("TestInt32Truncation", TestInt32Truncation), + DECLARE_NODE_API_PROPERTY("TestUint32Truncation", TestUint32Truncation), + DECLARE_NODE_API_PROPERTY("TestInt64Truncation", TestInt64Truncation), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + init_test_null(env, exports); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_object/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_object/CMakeLists.txt new file mode 100644 index 00000000..e8f81166 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/CMakeLists.txt @@ -0,0 +1,10 @@ +add_node_api_module(test_object + SOURCES + test_null.c + test_object.c +) + +add_node_api_module(test_exceptions + SOURCES + test_exceptions.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_object/binding.gyp b/Tests/NodeApi/test/js-native-api/test_object/binding.gyp new file mode 100644 index 00000000..37ea4931 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/binding.gyp @@ -0,0 +1,17 @@ +{ + "targets": [ + { + "target_name": "test_object", + "sources": [ + "test_null.c", + "test_object.c" + ] + }, + { + "target_name": "test_exceptions", + "sources": [ + "test_exceptions.c", + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_object/test.js b/Tests/NodeApi/test/js-native-api/test_object/test.js new file mode 100644 index 00000000..8ca961a1 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test.js @@ -0,0 +1,393 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for objects +const test_object = require(`./build/${common.buildType}/test_object`); + + +const object = { + hello: 'world', + array: [ + 1, 94, 'str', 12.321, { test: 'obj in arr' }, + ], + newObject: { + test: 'obj in obj', + }, +}; + +assert.strictEqual(test_object.Get(object, 'hello'), 'world'); +assert.strictEqual(test_object.GetNamed(object, 'hello'), 'world'); +assert.deepStrictEqual(test_object.Get(object, 'array'), + [1, 94, 'str', 12.321, { test: 'obj in arr' }]); +assert.deepStrictEqual(test_object.Get(object, 'newObject'), + { test: 'obj in obj' }); + +assert(test_object.Has(object, 'hello')); +assert(test_object.HasNamed(object, 'hello')); +assert(test_object.Has(object, 'array')); +assert(test_object.Has(object, 'newObject')); + +const newObject = test_object.New(); +assert(test_object.Has(newObject, 'test_number')); +assert.strictEqual(newObject.test_number, 987654321); +assert.strictEqual(newObject.test_string, 'test string'); + +{ + // Verify that napi_get_property() walks the prototype chain. + function MyObject() { + this.foo = 42; + this.bar = 43; + } + + MyObject.prototype.bar = 44; + MyObject.prototype.baz = 45; + + const obj = new MyObject(); + + assert.strictEqual(test_object.Get(obj, 'foo'), 42); + assert.strictEqual(test_object.Get(obj, 'bar'), 43); + assert.strictEqual(test_object.Get(obj, 'baz'), 45); + assert.strictEqual(test_object.Get(obj, 'toString'), + Object.prototype.toString); +} + +{ + // Verify that napi_has_own_property() fails if property is not a name. + [true, false, null, undefined, {}, [], 0, 1, () => { }].forEach((value) => { + assert.throws(() => { + test_object.HasOwn({}, value); + }, /^Error: A string or symbol was expected$/); + }); +} + +{ + // Verify that napi_has_own_property() does not walk the prototype chain. + const symbol1 = Symbol(); + const symbol2 = Symbol(); + + function MyObject() { + this.foo = 42; + this.bar = 43; + this[symbol1] = 44; + } + + MyObject.prototype.bar = 45; + MyObject.prototype.baz = 46; + MyObject.prototype[symbol2] = 47; + + const obj = new MyObject(); + + assert.strictEqual(test_object.HasOwn(obj, 'foo'), true); + assert.strictEqual(test_object.HasOwn(obj, 'bar'), true); + assert.strictEqual(test_object.HasOwn(obj, symbol1), true); + assert.strictEqual(test_object.HasOwn(obj, 'baz'), false); + assert.strictEqual(test_object.HasOwn(obj, 'toString'), false); + assert.strictEqual(test_object.HasOwn(obj, symbol2), false); +} + +{ + // test_object.Inflate increases all properties by 1 + const cube = { + x: 10, + y: 10, + z: 10, + }; + + assert.deepStrictEqual(test_object.Inflate(cube), { x: 11, y: 11, z: 11 }); + assert.deepStrictEqual(test_object.Inflate(cube), { x: 12, y: 12, z: 12 }); + assert.deepStrictEqual(test_object.Inflate(cube), { x: 13, y: 13, z: 13 }); + cube.t = 13; + assert.deepStrictEqual( + test_object.Inflate(cube), { x: 14, y: 14, z: 14, t: 14 }); + + const sym1 = Symbol('1'); + const sym2 = Symbol('2'); + const sym3 = Symbol('3'); + const sym4 = Symbol('4'); + const object2 = { + [sym1]: '@@iterator', + [sym2]: sym3, + }; + + assert(test_object.Has(object2, sym1)); + assert(test_object.Has(object2, sym2)); + assert.strictEqual(test_object.Get(object2, sym1), '@@iterator'); + assert.strictEqual(test_object.Get(object2, sym2), sym3); + assert(test_object.Set(object2, 'string', 'value')); + assert(test_object.SetNamed(object2, 'named_string', 'value')); + assert(test_object.Set(object2, sym4, 123)); + assert(test_object.Has(object2, 'string')); + assert(test_object.HasNamed(object2, 'named_string')); + assert(test_object.Has(object2, sym4)); + assert.strictEqual(test_object.Get(object2, 'string'), 'value'); + assert.strictEqual(test_object.Get(object2, sym4), 123); +} + +{ + // Wrap a pointer in a JS object, then verify the pointer can be unwrapped. + const wrapper = {}; + test_object.Wrap(wrapper); + + assert(test_object.Unwrap(wrapper)); +} + +{ + // Verify that wrapping doesn't break an object's prototype chain. + const wrapper = {}; + const protoA = { protoA: true }; + Object.setPrototypeOf(wrapper, protoA); + test_object.Wrap(wrapper); + + assert(test_object.Unwrap(wrapper)); + assert(wrapper.protoA); +} + +{ + // Verify the pointer can be unwrapped after inserting in the prototype chain. + const wrapper = {}; + const protoA = { protoA: true }; + Object.setPrototypeOf(wrapper, protoA); + test_object.Wrap(wrapper); + + const protoB = { protoB: true }; + Object.setPrototypeOf(protoB, Object.getPrototypeOf(wrapper)); + Object.setPrototypeOf(wrapper, protoB); + + assert(test_object.Unwrap(wrapper)); + assert(wrapper.protoA, true); + assert(wrapper.protoB, true); +} + +{ + // Verify that objects can be type-tagged and type-tag-checked. + const obj1 = test_object.TypeTaggedInstance(0); + const obj2 = test_object.TypeTaggedInstance(1); + const obj3 = test_object.TypeTaggedInstance(2); + const obj4 = test_object.TypeTaggedInstance(3); + const external = test_object.TypeTaggedExternal(2); + const plainExternal = test_object.PlainExternal(); + + // Verify that we do not allow type tag indices greater than the largest + // available index. + assert.throws(() => test_object.TypeTaggedInstance(39), { + name: 'RangeError', + message: 'Invalid type index', + }); + assert.throws(() => test_object.TypeTaggedExternal(39), { + name: 'RangeError', + message: 'Invalid type index', + }); + + // Verify that type tags are correctly accepted. + assert.strictEqual(test_object.CheckTypeTag(0, obj1), true); + assert.strictEqual(test_object.CheckTypeTag(1, obj2), true); + assert.strictEqual(test_object.CheckTypeTag(2, obj3), true); + assert.strictEqual(test_object.CheckTypeTag(3, obj4), true); + assert.strictEqual(test_object.CheckTypeTag(2, external), true); + + // Verify that wrongly tagged objects are rejected. + assert.strictEqual(test_object.CheckTypeTag(0, obj2), false); + assert.strictEqual(test_object.CheckTypeTag(1, obj1), false); + assert.strictEqual(test_object.CheckTypeTag(0, obj3), false); + assert.strictEqual(test_object.CheckTypeTag(1, obj4), false); + assert.strictEqual(test_object.CheckTypeTag(2, obj4), false); + assert.strictEqual(test_object.CheckTypeTag(3, obj3), false); + assert.strictEqual(test_object.CheckTypeTag(4, obj3), false); + assert.strictEqual(test_object.CheckTypeTag(0, external), false); + assert.strictEqual(test_object.CheckTypeTag(1, external), false); + assert.strictEqual(test_object.CheckTypeTag(3, external), false); + assert.strictEqual(test_object.CheckTypeTag(4, external), false); + + // Verify that untagged objects are rejected. + assert.strictEqual(test_object.CheckTypeTag(0, {}), false); + assert.strictEqual(test_object.CheckTypeTag(1, {}), false); + assert.strictEqual(test_object.CheckTypeTag(0, plainExternal), false); + assert.strictEqual(test_object.CheckTypeTag(1, plainExternal), false); + assert.strictEqual(test_object.CheckTypeTag(2, plainExternal), false); + assert.strictEqual(test_object.CheckTypeTag(3, plainExternal), false); + assert.strictEqual(test_object.CheckTypeTag(4, plainExternal), false); +} + +{ + // Verify that normal and nonexistent properties can be deleted. + const sym = Symbol(); + const obj = { foo: 'bar', [sym]: 'baz' }; + + assert.strictEqual('foo' in obj, true); + assert.strictEqual(sym in obj, true); + assert.strictEqual('does_not_exist' in obj, false); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual('foo' in obj, false); + assert.strictEqual(sym in obj, true); + assert.strictEqual('does_not_exist' in obj, false); + assert.strictEqual(test_object.Delete(obj, sym), true); + assert.strictEqual('foo' in obj, false); + assert.strictEqual(sym in obj, false); + assert.strictEqual('does_not_exist' in obj, false); +} + +{ + // Verify that non-configurable properties are not deleted. + const obj = {}; + + Object.defineProperty(obj, 'foo', { configurable: false }); + assert.strictEqual(test_object.Delete(obj, 'foo'), false); + assert.strictEqual('foo' in obj, true); +} + +{ + // Verify that prototype properties are not deleted. + function Foo() { + this.foo = 'bar'; + } + + Foo.prototype.foo = 'baz'; + + const obj = new Foo(); + + assert.strictEqual(obj.foo, 'bar'); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual(obj.foo, 'baz'); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual(obj.foo, 'baz'); +} + +{ + // Verify that napi_get_property_names gets the right set of property names, + // i.e.: includes prototypes, only enumerable properties, skips symbols, + // and includes indices and converts them to strings. + + const object = { __proto__: { + inherited: 1, + } }; + + const fooSymbol = Symbol('foo'); + + object.normal = 2; + object[fooSymbol] = 3; + Object.defineProperty(object, 'unenumerable', { + value: 4, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(object, 'writable', { + value: 4, + enumerable: true, + writable: true, + configurable: false, + }); + Object.defineProperty(object, 'configurable', { + value: 4, + enumerable: true, + writable: false, + configurable: true, + }); + object[5] = 5; + + assert.deepStrictEqual(test_object.GetPropertyNames(object), + ['5', + 'normal', + 'writable', + 'configurable', + 'inherited']); + + assert.deepStrictEqual(test_object.GetSymbolNames(object), + [fooSymbol]); + + assert.deepStrictEqual(test_object.GetEnumerableWritableNames(object), + ['5', + 'normal', + 'writable', + fooSymbol, + 'inherited']); + + assert.deepStrictEqual(test_object.GetOwnWritableNames(object), + ['5', + 'normal', + 'unenumerable', + 'writable', + fooSymbol]); + + assert.deepStrictEqual(test_object.GetEnumerableConfigurableNames(object), + ['5', + 'normal', + 'configurable', + fooSymbol, + 'inherited']); + + assert.deepStrictEqual(test_object.GetOwnConfigurableNames(object), + ['5', + 'normal', + 'unenumerable', + 'configurable', + fooSymbol]); +} + +// Verify that passing NULL to napi_set_property() results in the correct +// error. +assert.deepStrictEqual(test_object.TestSetProperty(), { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + keyIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', +}); + +// Verify that passing NULL to napi_has_property() results in the correct +// error. +assert.deepStrictEqual(test_object.TestHasProperty(), { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + keyIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}); + +// Verify that passing NULL to napi_get_property() results in the correct +// error. +assert.deepStrictEqual(test_object.TestGetProperty(), { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + keyIsNull: 'Invalid argument', + resultIsNull: 'Invalid argument', +}); + +{ + const obj = { x: 'a', y: 'b', z: 'c' }; + + test_object.TestSeal(obj); + + assert.strictEqual(Object.isSealed(obj), true); + + assert.throws(() => { + obj.w = 'd'; + }, /(Cannot add property w, object is not extensible)|(TypeError: Cannot add new property 'w')/); + + assert.throws(() => { + delete obj.x; + }, /(Cannot delete property 'x' of #)|(TypeError: Property is not configurable)/); + + // Sealed objects allow updating existing properties, + // so this should not throw. + obj.x = 'd'; +} + +{ + const obj = { x: 10, y: 10, z: 10 }; + + test_object.TestFreeze(obj); + + assert.strictEqual(Object.isFrozen(obj), true); + + assert.throws(() => { + obj.x = 10; + }, /(Cannot assign to read only property 'x' of object '#)|(TypeError: Cannot assign to read-only property 'x')/); + + assert.throws(() => { + obj.w = 15; + }, /(Cannot add property w, object is not extensible)|(TypeError: Cannot add new property 'w')/); + + assert.throws(() => { + delete obj.x; + }, /(Cannot delete property 'x' of #)|(TypeError: Property is not configurable)/); +} diff --git a/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.c b/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.c new file mode 100644 index 00000000..7474d49e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value TestExceptions(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 target = args[0]; + napi_value exception, key, value; + napi_status status; + bool is_exception_pending; + bool bool_result; + + NODE_API_CALL(env, + napi_create_string_utf8(env, "key", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL( + env, napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &value)); + +#define PROCEDURE(call) \ + { \ + status = (call); \ + NODE_API_ASSERT( \ + env, status == napi_pending_exception, "expect exception pending"); \ + NODE_API_CALL(env, napi_is_exception_pending(env, &is_exception_pending)); \ + NODE_API_ASSERT(env, is_exception_pending, "expect exception pending"); \ + NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &exception)); \ + } + // discard the exception values. + + // properties + PROCEDURE(napi_set_property(env, target, key, value)); + PROCEDURE(napi_set_named_property(env, target, "key", value)); + PROCEDURE(napi_has_property(env, target, key, &bool_result)); + PROCEDURE(napi_has_own_property(env, target, key, &bool_result)); + PROCEDURE(napi_has_named_property(env, target, "key", &bool_result)); + PROCEDURE(napi_get_property(env, target, key, &value)); + PROCEDURE(napi_get_named_property(env, target, "key", &value)); + PROCEDURE(napi_delete_property(env, target, key, &bool_result)); + + // elements + PROCEDURE(napi_set_element(env, target, 0, value)); + PROCEDURE(napi_has_element(env, target, 0, &bool_result)); + PROCEDURE(napi_get_element(env, target, 0, &value)); + PROCEDURE(napi_delete_element(env, target, 0, &bool_result)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY_VALUE("key", value), + }; + PROCEDURE(napi_define_properties( + env, target, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + PROCEDURE(napi_get_all_property_names(env, + target, + napi_key_own_only, + napi_key_enumerable, + napi_key_keep_numbers, + &value)); + PROCEDURE(napi_get_property_names(env, target, &value)); + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("testExceptions", TestExceptions), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.js b/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.js new file mode 100644 index 00000000..2d5f10ab --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_exceptions.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); + +// Test +const { testExceptions } = require(`./build/${common.buildType}/test_exceptions`); + +function throws() { + throw new Error('foobar'); +} +testExceptions(new Proxy({}, { + get: common.mustCallAtLeast(throws, 1), + getOwnPropertyDescriptor: common.mustCallAtLeast(throws, 1), + defineProperty: common.mustCallAtLeast(throws, 1), + deleteProperty: common.mustCallAtLeast(throws, 1), + has: common.mustCallAtLeast(throws, 1), + set: common.mustCallAtLeast(throws, 1), + ownKeys: common.mustCallAtLeast(throws, 1), +})); diff --git a/Tests/NodeApi/test/js-native-api/test_object/test_null.c b/Tests/NodeApi/test/js-native-api/test_object/test_null.c new file mode 100644 index 00000000..4fd4e95e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_null.c @@ -0,0 +1,400 @@ +#include + +#include "../common.h" +#include "test_null.h" + +static napi_value SetProperty(napi_env env, napi_callback_info info) { + napi_value return_value, object, key; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + NODE_API_CALL(env, + napi_create_string_utf8(env, "someString", NAPI_AUTO_LENGTH, &key)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_set_property(NULL, object, key, object)); + + napi_set_property(env, NULL, key, object); + add_last_status(env, "objectIsNull", return_value); + + napi_set_property(env, object, NULL, object); + add_last_status(env, "keyIsNull", return_value); + + napi_set_property(env, object, key, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value GetProperty(napi_env env, napi_callback_info info) { + napi_value return_value, object, key, prop; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + NODE_API_CALL(env, + napi_create_string_utf8(env, "someString", NAPI_AUTO_LENGTH, &key)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_property(NULL, object, key, &prop)); + + napi_get_property(env, NULL, key, &prop); + add_last_status(env, "objectIsNull", return_value); + + napi_get_property(env, object, NULL, &prop); + add_last_status(env, "keyIsNull", return_value); + + napi_get_property(env, object, key, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value TestBoolValuedPropApi(napi_env env, + napi_status (*api)(napi_env, napi_value, napi_value, bool*)) { + napi_value return_value, object, key; + bool result; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + NODE_API_CALL(env, + napi_create_string_utf8(env, "someString", NAPI_AUTO_LENGTH, &key)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + api(NULL, object, key, &result)); + + api(env, NULL, key, &result); + add_last_status(env, "objectIsNull", return_value); + + api(env, object, NULL, &result); + add_last_status(env, "keyIsNull", return_value); + + api(env, object, key, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value HasProperty(napi_env env, napi_callback_info info) { + return TestBoolValuedPropApi(env, napi_has_property); +} + +static napi_value HasOwnProperty(napi_env env, napi_callback_info info) { + return TestBoolValuedPropApi(env, napi_has_own_property); +} + +static napi_value DeleteProperty(napi_env env, napi_callback_info info) { + return TestBoolValuedPropApi(env, napi_delete_property); +} + +static napi_value SetNamedProperty(napi_env env, napi_callback_info info) { + napi_value return_value, object; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_set_named_property(NULL, object, "key", object)); + + napi_set_named_property(env, NULL, "key", object); + add_last_status(env, "objectIsNull", return_value); + + napi_set_named_property(env, object, NULL, object); + add_last_status(env, "keyIsNull", return_value); + + napi_set_named_property(env, object, "key", NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value GetNamedProperty(napi_env env, napi_callback_info info) { + napi_value return_value, object, prop; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_named_property(NULL, object, "key", &prop)); + + napi_get_named_property(env, NULL, "key", &prop); + add_last_status(env, "objectIsNull", return_value); + + napi_get_named_property(env, object, NULL, &prop); + add_last_status(env, "keyIsNull", return_value); + + napi_get_named_property(env, object, "key", NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value HasNamedProperty(napi_env env, napi_callback_info info) { + napi_value return_value, object; + bool result; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_has_named_property(NULL, object, "key", &result)); + + napi_has_named_property(env, NULL, "key", &result); + add_last_status(env, "objectIsNull", return_value); + + napi_has_named_property(env, object, NULL, &result); + add_last_status(env, "keyIsNull", return_value); + + napi_has_named_property(env, object, "key", NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value SetElement(napi_env env, napi_callback_info info) { + napi_value return_value, object; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_set_element(NULL, object, 0, object)); + + napi_set_element(env, NULL, 0, object); + add_last_status(env, "objectIsNull", return_value); + + napi_set_property(env, object, 0, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value GetElement(napi_env env, napi_callback_info info) { + napi_value return_value, object, prop; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_element(NULL, object, 0, &prop)); + + napi_get_property(env, NULL, 0, &prop); + add_last_status(env, "objectIsNull", return_value); + + napi_get_property(env, object, 0, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value TestBoolValuedElementApi(napi_env env, + napi_status (*api)(napi_env, napi_value, uint32_t, bool*)) { + napi_value return_value, object; + bool result; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + NODE_API_CALL(env, napi_create_object(env, &object)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + api(NULL, object, 0, &result)); + + api(env, NULL, 0, &result); + add_last_status(env, "objectIsNull", return_value); + + api(env, object, 0, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value HasElement(napi_env env, napi_callback_info info) { + return TestBoolValuedElementApi(env, napi_has_element); +} + +static napi_value DeleteElement(napi_env env, napi_callback_info info) { + return TestBoolValuedElementApi(env, napi_delete_element); +} + +static napi_value DefineProperties(napi_env env, napi_callback_info info) { + napi_value object, return_value; + + napi_property_descriptor desc = { + "prop", NULL, DefineProperties, NULL, NULL, NULL, napi_enumerable, NULL + }; + + NODE_API_CALL(env, napi_create_object(env, &object)); + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_define_properties(NULL, object, 1, &desc)); + + napi_define_properties(env, NULL, 1, &desc); + add_last_status(env, "objectIsNull", return_value); + + napi_define_properties(env, object, 1, NULL); + add_last_status(env, "descriptorListIsNull", return_value); + + desc.utf8name = NULL; + napi_define_properties(env, object, 1, NULL); + add_last_status(env, "utf8nameIsNull", return_value); + desc.utf8name = "prop"; + + desc.method = NULL; + napi_define_properties(env, object, 1, NULL); + add_last_status(env, "methodIsNull", return_value); + desc.method = DefineProperties; + + return return_value; +} + +static napi_value GetPropertyNames(napi_env env, napi_callback_info info) { + napi_value return_value, props; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_property_names(NULL, return_value, &props)); + + napi_get_property_names(env, NULL, &props); + add_last_status(env, "objectIsNull", return_value); + + napi_get_property_names(env, return_value, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value GetAllPropertyNames(napi_env env, napi_callback_info info) { + napi_value return_value, props; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_all_property_names(NULL, + return_value, + napi_key_own_only, + napi_key_writable, + napi_key_keep_numbers, + &props)); + + napi_get_all_property_names(env, + NULL, + napi_key_own_only, + napi_key_writable, + napi_key_keep_numbers, + &props); + add_last_status(env, "objectIsNull", return_value); + + napi_get_all_property_names(env, + return_value, + napi_key_own_only, + napi_key_writable, + napi_key_keep_numbers, + NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +static napi_value GetPrototype(napi_env env, napi_callback_info info) { + napi_value return_value, proto; + + NODE_API_CALL(env, napi_create_object(env, &return_value)); + + add_returned_status(env, + "envIsNull", + return_value, + "Invalid argument", + napi_invalid_arg, + napi_get_prototype(NULL, return_value, &proto)); + + napi_get_prototype(env, NULL, &proto); + add_last_status(env, "objectIsNull", return_value); + + napi_get_prototype(env, return_value, NULL); + add_last_status(env, "valueIsNull", return_value); + + return return_value; +} + +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("setProperty", SetProperty), + DECLARE_NODE_API_PROPERTY("getProperty", GetProperty), + DECLARE_NODE_API_PROPERTY("hasProperty", HasProperty), + DECLARE_NODE_API_PROPERTY("hasOwnProperty", HasOwnProperty), + DECLARE_NODE_API_PROPERTY("deleteProperty", DeleteProperty), + DECLARE_NODE_API_PROPERTY("setNamedProperty", SetNamedProperty), + DECLARE_NODE_API_PROPERTY("getNamedProperty", GetNamedProperty), + DECLARE_NODE_API_PROPERTY("hasNamedProperty", HasNamedProperty), + DECLARE_NODE_API_PROPERTY("setElement", SetElement), + DECLARE_NODE_API_PROPERTY("getElement", GetElement), + DECLARE_NODE_API_PROPERTY("hasElement", HasElement), + DECLARE_NODE_API_PROPERTY("deleteElement", DeleteElement), + DECLARE_NODE_API_PROPERTY("defineProperties", DefineProperties), + DECLARE_NODE_API_PROPERTY("getPropertyNames", GetPropertyNames), + DECLARE_NODE_API_PROPERTY("getAllPropertyNames", GetAllPropertyNames), + DECLARE_NODE_API_PROPERTY("getPrototype", GetPrototype), + }; + + 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/NodeApi/test/js-native-api/test_object/test_null.h b/Tests/NodeApi/test/js-native-api/test_object/test_null.h new file mode 100644 index 00000000..b142570d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_null.h @@ -0,0 +1,8 @@ +#ifndef TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ +#define TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ + +#include + +void init_test_null(napi_env env, napi_value exports); + +#endif // TEST_JS_NATIVE_API_TEST_OBJECT_TEST_NULL_H_ diff --git a/Tests/NodeApi/test/js-native-api/test_object/test_null.js b/Tests/NodeApi/test/js-native-api/test_object/test_null.js new file mode 100644 index 00000000..dcf688c5 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_null.js @@ -0,0 +1,53 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Test passing NULL to object-related N-APIs. +const { testNull } = require(`./build/${common.buildType}/test_object`); + +const expectedForProperty = { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + keyIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', +}; +assert.deepStrictEqual(testNull.setProperty(), expectedForProperty); +assert.deepStrictEqual(testNull.getProperty(), expectedForProperty); +assert.deepStrictEqual(testNull.hasProperty(), expectedForProperty); +// eslint-disable-next-line no-prototype-builtins +assert.deepStrictEqual(testNull.hasOwnProperty(), expectedForProperty); +// It's OK not to want the result of a deletion. +assert.deepStrictEqual(testNull.deleteProperty(), + Object.assign({}, + expectedForProperty, + { valueIsNull: 'napi_ok' })); +assert.deepStrictEqual(testNull.setNamedProperty(), expectedForProperty); +assert.deepStrictEqual(testNull.getNamedProperty(), expectedForProperty); +assert.deepStrictEqual(testNull.hasNamedProperty(), expectedForProperty); + +const expectedForElement = { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + valueIsNull: 'Invalid argument', +}; +assert.deepStrictEqual(testNull.setElement(), expectedForElement); +assert.deepStrictEqual(testNull.getElement(), expectedForElement); +assert.deepStrictEqual(testNull.hasElement(), expectedForElement); +// It's OK not to want the result of a deletion. +assert.deepStrictEqual(testNull.deleteElement(), + Object.assign({}, + expectedForElement, + { valueIsNull: 'napi_ok' })); + +assert.deepStrictEqual(testNull.defineProperties(), { + envIsNull: 'Invalid argument', + objectIsNull: 'Invalid argument', + descriptorListIsNull: 'Invalid argument', + utf8nameIsNull: 'Invalid argument', + methodIsNull: 'Invalid argument', +}); + +// `expectedForElement` also works for the APIs below. +assert.deepStrictEqual(testNull.getPropertyNames(), expectedForElement); +assert.deepStrictEqual(testNull.getAllPropertyNames(), expectedForElement); +assert.deepStrictEqual(testNull.getPrototype(), expectedForElement); diff --git a/Tests/NodeApi/test/js-native-api/test_object/test_object.c b/Tests/NodeApi/test/js-native-api/test_object/test_object.c new file mode 100644 index 00000000..4d53e09f --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_object/test_object.c @@ -0,0 +1,755 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" +#include "test_null.h" + +static int test_value = 3; + +static napi_value Get(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + napi_value object = args[0]; + napi_value output; + NODE_API_CALL(env, napi_get_property(env, object, args[1], &output)); + + return output; +} + +static napi_value GetNamed(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + char key[256] = ""; + size_t key_length; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT(env, value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype value_type1; + NODE_API_CALL(env, napi_typeof(env, args[1], &value_type1)); + + NODE_API_ASSERT(env, value_type1 == napi_string, + "Wrong type of arguments. Expects a string as second."); + + napi_value object = args[0]; + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[1], key, 255, &key_length)); + key[255] = 0; + NODE_API_ASSERT(env, key_length <= 255, + "Cannot accommodate keys longer than 255 bytes"); + napi_value output; + NODE_API_CALL(env, napi_get_named_property(env, object, key, &output)); + + return output; +} + +static napi_value GetPropertyNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT(env, value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL(env, napi_get_property_names(env, args[0], &output)); + + return output; +} + +static napi_value GetSymbolNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT(env, + value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL(env, + napi_get_all_property_names( + env, args[0], napi_key_include_prototypes, napi_key_skip_strings, + napi_key_numbers_to_strings, &output)); + + return output; +} + +static napi_value GetEnumerableWritableNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT( + env, + value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL( + env, + napi_get_all_property_names(env, + args[0], + napi_key_include_prototypes, + napi_key_enumerable | napi_key_writable, + napi_key_numbers_to_strings, + &output)); + + return output; +} + +static napi_value GetOwnWritableNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT( + env, + value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL(env, + napi_get_all_property_names(env, + args[0], + napi_key_own_only, + napi_key_writable, + napi_key_numbers_to_strings, + &output)); + + return output; +} + +static napi_value GetEnumerableConfigurableNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT( + env, + value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL( + env, + napi_get_all_property_names(env, + args[0], + napi_key_include_prototypes, + napi_key_enumerable | napi_key_configurable, + napi_key_numbers_to_strings, + &output)); + + return output; +} + +static napi_value GetOwnConfigurableNames(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 value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT( + env, + value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value output; + NODE_API_CALL(env, + napi_get_all_property_names(env, + args[0], + napi_key_own_only, + napi_key_configurable, + napi_key_numbers_to_strings, + &output)); + + return output; +} + +static napi_value Set(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args[3]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 3, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + NODE_API_CALL(env, napi_set_property(env, args[0], args[1], args[2])); + + napi_value valuetrue; + NODE_API_CALL(env, napi_get_boolean(env, true, &valuetrue)); + + return valuetrue; +} + +static napi_value SetNamed(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args[3]; + char key[256] = ""; + size_t key_length; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 3, "Wrong number of arguments"); + + napi_valuetype value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT(env, value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype value_type1; + NODE_API_CALL(env, napi_typeof(env, args[1], &value_type1)); + + NODE_API_ASSERT(env, value_type1 == napi_string, + "Wrong type of arguments. Expects a string as second."); + + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[1], key, 255, &key_length)); + key[255] = 0; + NODE_API_ASSERT(env, key_length <= 255, + "Cannot accommodate keys longer than 255 bytes"); + + NODE_API_CALL(env, napi_set_named_property(env, args[0], key, args[2])); + + napi_value value_true; + NODE_API_CALL(env, napi_get_boolean(env, true, &value_true)); + + return value_true; +} + +static napi_value Has(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + bool has_property; + NODE_API_CALL(env, napi_has_property(env, args[0], args[1], &has_property)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, has_property, &ret)); + + return ret; +} + +static napi_value HasNamed(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + char key[256] = ""; + size_t key_length; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype value_type0; + NODE_API_CALL(env, napi_typeof(env, args[0], &value_type0)); + + NODE_API_ASSERT(env, value_type0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype value_type1; + NODE_API_CALL(env, napi_typeof(env, args[1], &value_type1)); + + NODE_API_ASSERT(env, value_type1 == napi_string || value_type1 == napi_symbol, + "Wrong type of arguments. Expects a string as second."); + + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[1], key, 255, &key_length)); + key[255] = 0; + NODE_API_ASSERT(env, key_length <= 255, + "Cannot accommodate keys longer than 255 bytes"); + + bool has_property; + NODE_API_CALL(env, napi_has_named_property(env, args[0], key, &has_property)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, has_property, &ret)); + + return ret; +} + +static napi_value HasOwn(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + // napi_valuetype valuetype1; + // NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + // + // NODE_API_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + // "Wrong type of arguments. Expects a string or symbol as second."); + + bool has_property; + NODE_API_CALL(env, napi_has_own_property(env, args[0], args[1], &has_property)); + + napi_value ret; + NODE_API_CALL(env, napi_get_boolean(env, has_property, &ret)); + + return ret; +} + +static napi_value Delete(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NODE_API_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + bool result; + napi_value ret; + NODE_API_CALL(env, napi_delete_property(env, args[0], args[1], &result)); + NODE_API_CALL(env, napi_get_boolean(env, result, &ret)); + + return ret; +} + +static napi_value New(napi_env env, napi_callback_info info) { + napi_value ret; + NODE_API_CALL(env, napi_create_object(env, &ret)); + + napi_value num; + NODE_API_CALL(env, napi_create_int32(env, 987654321, &num)); + + NODE_API_CALL(env, napi_set_named_property(env, ret, "test_number", num)); + + napi_value str; + const char* str_val = "test string"; + size_t str_len = strlen(str_val); + NODE_API_CALL(env, napi_create_string_utf8(env, str_val, str_len, &str)); + + NODE_API_CALL(env, napi_set_named_property(env, ret, "test_string", str)); + + return ret; +} + +static napi_value Inflate(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value obj = args[0]; + napi_value propertynames; + NODE_API_CALL(env, napi_get_property_names(env, obj, &propertynames)); + + uint32_t i, length; + NODE_API_CALL(env, napi_get_array_length(env, propertynames, &length)); + + for (i = 0; i < length; i++) { + napi_value property_str; + NODE_API_CALL(env, napi_get_element(env, propertynames, i, &property_str)); + + napi_value value; + NODE_API_CALL(env, napi_get_property(env, obj, property_str, &value)); + + double double_val; + NODE_API_CALL(env, napi_get_value_double(env, value, &double_val)); + NODE_API_CALL(env, napi_create_double(env, double_val + 1, &value)); + NODE_API_CALL(env, napi_set_property(env, obj, property_str, value)); + } + + return obj; +} + +static napi_value Wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + NODE_API_CALL(env, napi_wrap(env, arg, &test_value, NULL, NULL, NULL)); + return NULL; +} + +static napi_value Unwrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + void* data; + NODE_API_CALL(env, napi_unwrap(env, arg, &data)); + + bool is_expected = (data != NULL && *(int*)data == 3); + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, is_expected, &result)); + return result; +} + +static napi_value TestSetProperty(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value object, key, value; + + NODE_API_CALL(env, napi_create_object(env, &object)); + + NODE_API_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &key)); + + NODE_API_CALL(env, napi_create_object(env, &value)); + + status = napi_set_property(NULL, object, key, value); + + add_returned_status(env, + "envIsNull", + object, + "Invalid argument", + napi_invalid_arg, + status); + + napi_set_property(env, NULL, key, value); + + add_last_status(env, "objectIsNull", object); + + napi_set_property(env, object, NULL, value); + + add_last_status(env, "keyIsNull", object); + + napi_set_property(env, object, key, NULL); + + add_last_status(env, "valueIsNull", object); + + return object; +} + +static napi_value TestHasProperty(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value object, key; + bool result; + + NODE_API_CALL(env, napi_create_object(env, &object)); + + NODE_API_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &key)); + + status = napi_has_property(NULL, object, key, &result); + + add_returned_status(env, + "envIsNull", + object, + "Invalid argument", + napi_invalid_arg, + status); + + napi_has_property(env, NULL, key, &result); + + add_last_status(env, "objectIsNull", object); + + napi_has_property(env, object, NULL, &result); + + add_last_status(env, "keyIsNull", object); + + napi_has_property(env, object, key, NULL); + + add_last_status(env, "resultIsNull", object); + + return object; +} + +static napi_value TestGetProperty(napi_env env, + napi_callback_info info) { + napi_status status; + napi_value object, key, result; + + NODE_API_CALL(env, napi_create_object(env, &object)); + + NODE_API_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &key)); + + NODE_API_CALL(env, napi_create_object(env, &result)); + + status = napi_get_property(NULL, object, key, &result); + + add_returned_status(env, + "envIsNull", + object, + "Invalid argument", + napi_invalid_arg, + status); + + napi_get_property(env, NULL, key, &result); + + add_last_status(env, "objectIsNull", object); + + napi_get_property(env, object, NULL, &result); + + add_last_status(env, "keyIsNull", object); + + napi_get_property(env, object, key, NULL); + + add_last_status(env, "resultIsNull", object); + + return object; +} + +static napi_value TestFreeze(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 object = args[0]; + NODE_API_CALL(env, napi_object_freeze(env, object)); + + return object; +} + +static napi_value TestSeal(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 object = args[0]; + NODE_API_CALL(env, napi_object_seal(env, object)); + + return object; +} + +// We create two type tags. They are basically 128-bit UUIDs. +#define TYPE_TAG_COUNT 5 +static const napi_type_tag type_tags[TYPE_TAG_COUNT] = { + {0xdaf987b3cc62481a, 0xb745b0497f299531}, + {0xbb7936c374084d9b, 0xa9548d0762eeedb9}, + {0xa5ed9ce2e4c00c38, 0}, + {0, 0}, + {0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a}, +}; +#define VALIDATE_TYPE_INDEX(env, type_index) \ + do { \ + if ((type_index) >= TYPE_TAG_COUNT) { \ + NODE_API_CALL((env), \ + napi_throw_range_error((env), \ + "NODE_API_TEST_INVALID_TYPE_INDEX", \ + "Invalid type index")); \ + } \ + } while (0) + +static napi_value +TypeTaggedInstance(napi_env env, napi_callback_info info) { + size_t argc = 1; + uint32_t type_index; + napi_value instance, which_type; + napi_type_tag tag; + + // Below we copy the tag before setting it to prevent bugs where a pointer + // to the tag (instead of the 128-bit tag value) is stored. + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &which_type, NULL, NULL)); + NODE_API_CALL(env, napi_get_value_uint32(env, which_type, &type_index)); + VALIDATE_TYPE_INDEX(env, type_index); + NODE_API_CALL(env, napi_create_object(env, &instance)); + tag = type_tags[type_index]; + NODE_API_CALL(env, napi_type_tag_object(env, instance, &tag)); + + // Since the tag passed to napi_type_tag_object() was copied to the stack, + // a type tagging implementation that uses a pointer instead of the + // tag value would end up pointing to stack memory. + // When CheckTypeTag() is called later on, it might be the case that this + // stack address has been left untouched by accident (if no subsequent + // function call has clobbered it), which means the pointer would still + // point to valid data. + // To make sure that tags are stored by value and not by reference, + // clear this copy; any implementation using a pointer would end up with + // random stack data or { 0, 0 }, but not the original tag value, and fail. + memset(&tag, 0, sizeof(tag)); + + return instance; +} + +// V8 will not allow us to construct an external with a NULL data value. +#define IN_LIEU_OF_NULL ((void*)0x1) + +static napi_value PlainExternal(napi_env env, napi_callback_info info) { + napi_value instance; + + NODE_API_CALL( + env, napi_create_external(env, IN_LIEU_OF_NULL, NULL, NULL, &instance)); + + return instance; +} + +static napi_value TypeTaggedExternal(napi_env env, napi_callback_info info) { + size_t argc = 1; + uint32_t type_index; + napi_value instance, which_type; + napi_type_tag tag; + + // See TypeTaggedInstance() for an explanation about why we copy the tag + // to the stack and why we call memset on it after the external is tagged. + + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, &which_type, NULL, NULL)); + NODE_API_CALL(env, napi_get_value_uint32(env, which_type, &type_index)); + VALIDATE_TYPE_INDEX(env, type_index); + NODE_API_CALL( + env, napi_create_external(env, IN_LIEU_OF_NULL, NULL, NULL, &instance)); + tag = type_tags[type_index]; + NODE_API_CALL(env, napi_type_tag_object(env, instance, &tag)); + + memset(&tag, 0, sizeof(tag)); + + return instance; +} + +static napi_value +CheckTypeTag(napi_env env, napi_callback_info info) { + size_t argc = 2; + bool result; + napi_value argv[2], js_result; + uint32_t type_index; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_value_uint32(env, argv[0], &type_index)); + VALIDATE_TYPE_INDEX(env, type_index); + NODE_API_CALL(env, napi_check_object_type_tag(env, + argv[1], + &type_tags[type_index], + &result)); + NODE_API_CALL(env, napi_get_boolean(env, result, &js_result)); + + return js_result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("Get", Get), + DECLARE_NODE_API_PROPERTY("GetNamed", GetNamed), + DECLARE_NODE_API_PROPERTY("GetPropertyNames", GetPropertyNames), + DECLARE_NODE_API_PROPERTY("GetSymbolNames", GetSymbolNames), + DECLARE_NODE_API_PROPERTY("GetEnumerableWritableNames", + GetEnumerableWritableNames), + DECLARE_NODE_API_PROPERTY("GetOwnWritableNames", GetOwnWritableNames), + DECLARE_NODE_API_PROPERTY("GetEnumerableConfigurableNames", + GetEnumerableConfigurableNames), + DECLARE_NODE_API_PROPERTY("GetOwnConfigurableNames", + GetOwnConfigurableNames), + DECLARE_NODE_API_PROPERTY("Set", Set), + DECLARE_NODE_API_PROPERTY("SetNamed", SetNamed), + DECLARE_NODE_API_PROPERTY("Has", Has), + DECLARE_NODE_API_PROPERTY("HasNamed", HasNamed), + DECLARE_NODE_API_PROPERTY("HasOwn", HasOwn), + DECLARE_NODE_API_PROPERTY("Delete", Delete), + DECLARE_NODE_API_PROPERTY("New", New), + DECLARE_NODE_API_PROPERTY("Inflate", Inflate), + DECLARE_NODE_API_PROPERTY("Wrap", Wrap), + DECLARE_NODE_API_PROPERTY("Unwrap", Unwrap), + DECLARE_NODE_API_PROPERTY("TestSetProperty", TestSetProperty), + DECLARE_NODE_API_PROPERTY("TestHasProperty", TestHasProperty), + DECLARE_NODE_API_PROPERTY("TypeTaggedInstance", TypeTaggedInstance), + DECLARE_NODE_API_PROPERTY("TypeTaggedExternal", TypeTaggedExternal), + DECLARE_NODE_API_PROPERTY("PlainExternal", PlainExternal), + DECLARE_NODE_API_PROPERTY("CheckTypeTag", CheckTypeTag), + DECLARE_NODE_API_PROPERTY("TestGetProperty", TestGetProperty), + DECLARE_NODE_API_PROPERTY("TestFreeze", TestFreeze), + DECLARE_NODE_API_PROPERTY("TestSeal", TestSeal), + }; + + init_test_null(env, exports); + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_promise/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_promise/CMakeLists.txt new file mode 100644 index 00000000..0e82dff9 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_promise/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_promise + SOURCES + test_promise.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_promise/binding.gyp b/Tests/NodeApi/test/js-native-api/test_promise/binding.gyp new file mode 100644 index 00000000..c2b65f5a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_promise/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_promise", + "sources": [ + "test_promise.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_promise/test.js b/Tests/NodeApi/test/js-native-api/test_promise/test.js new file mode 100644 index 00000000..695fdcc2 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_promise/test.js @@ -0,0 +1,61 @@ +'use strict'; + +const common = require('../../common'); + +// This tests the promise-related n-api calls + +const assert = require('assert'); +const test_promise = require(`./build/${common.buildType}/test_promise`); + +// A resolution +{ + const expected_result = 42; + const promise = test_promise.createPromise(); + promise.then( + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + }), + common.mustNotCall()); + test_promise.concludeCurrentPromise(expected_result, true); +} + +// A rejection +{ + const expected_result = 'It\'s not you, it\'s me.'; + const promise = test_promise.createPromise(); + promise.then( + common.mustNotCall(), + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + })); + test_promise.concludeCurrentPromise(expected_result, false); +} + +// Chaining +{ + const expected_result = 'chained answer'; + const promise = test_promise.createPromise(); + promise.then( + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + }), + common.mustNotCall()); + test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); +} + +const promiseTypeTestPromise = test_promise.createPromise(); +assert.strictEqual(test_promise.isPromise(promiseTypeTestPromise), true); +test_promise.concludeCurrentPromise(undefined, true); + +const rejectPromise = Promise.reject(-1); +const expected_reason = -1; +assert.strictEqual(test_promise.isPromise(rejectPromise), true); +rejectPromise.catch((reason) => { + assert.strictEqual(reason, expected_reason); +}); + +assert.strictEqual(test_promise.isPromise(2.4), false); +assert.strictEqual(test_promise.isPromise('I promise!'), false); +assert.strictEqual(test_promise.isPromise(undefined), false); +assert.strictEqual(test_promise.isPromise(null), false); +assert.strictEqual(test_promise.isPromise({}), false); diff --git a/Tests/NodeApi/test/js-native-api/test_promise/test_promise.c b/Tests/NodeApi/test/js-native-api/test_promise/test_promise.c new file mode 100644 index 00000000..1f0b5507 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_promise/test_promise.c @@ -0,0 +1,64 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +napi_deferred deferred = NULL; + +static napi_value createPromise(napi_env env, napi_callback_info info) { + napi_value promise; + + // We do not overwrite an existing deferred. + if (deferred != NULL) { + return NULL; + } + + NODE_API_CALL(env, napi_create_promise(env, &deferred, &promise)); + + return promise; +} + +static napi_value +concludeCurrentPromise(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + bool resolution; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NODE_API_CALL(env, napi_get_value_bool(env, argv[1], &resolution)); + if (resolution) { + NODE_API_CALL(env, napi_resolve_deferred(env, deferred, argv[0])); + } else { + NODE_API_CALL(env, napi_reject_deferred(env, deferred, argv[0])); + } + + deferred = NULL; + + return NULL; +} + +static napi_value isPromise(napi_env env, napi_callback_info info) { + napi_value promise, result; + size_t argc = 1; + bool is_promise; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &promise, NULL, NULL)); + NODE_API_CALL(env, napi_is_promise(env, promise, &is_promise)); + NODE_API_CALL(env, napi_get_boolean(env, is_promise, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("createPromise", createPromise), + DECLARE_NODE_API_PROPERTY("concludeCurrentPromise", concludeCurrentPromise), + DECLARE_NODE_API_PROPERTY("isPromise", isPromise), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_properties/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_properties/CMakeLists.txt new file mode 100644 index 00000000..18dcbf18 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_properties/CMakeLists.txt @@ -0,0 +1,6 @@ +add_node_api_module(test_properties + SOURCES + test_properties.c + DEFINES + "NAPI_VERSION=9" +) diff --git a/Tests/NodeApi/test/js-native-api/test_properties/binding.gyp b/Tests/NodeApi/test/js-native-api/test_properties/binding.gyp new file mode 100644 index 00000000..ab9d58db --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_properties/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_properties", + "sources": [ + "test_properties.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_properties/test.js b/Tests/NodeApi/test/js-native-api/test_properties/test.js new file mode 100644 index 00000000..6e035d67 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_properties/test.js @@ -0,0 +1,69 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const readonlyErrorRE = + /^TypeError: Cannot assign to read(-| )only property '.*'( of object '#')?$/; +const getterOnlyErrorRE = + /^TypeError: Cannot (set|assign to) property .*( of #)? which has only a getter$/; + +// Testing api calls for defining properties +const test_object = require(`./build/${common.buildType}/test_properties`); + +assert.strictEqual(test_object.echo('hello'), 'hello'); + +test_object.readwriteValue = 1; +assert.strictEqual(test_object.readwriteValue, 1); +test_object.readwriteValue = 2; +assert.strictEqual(test_object.readwriteValue, 2); + +assert.throws(() => { test_object.readonlyValue = 3; }, readonlyErrorRE); + +assert.ok(test_object.hiddenValue); + +// Properties with napi_enumerable attribute should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.includes('echo')); +assert.ok(propertyNames.includes('readwriteValue')); +assert.ok(propertyNames.includes('readonlyValue')); +assert.ok(!propertyNames.includes('hiddenValue')); +assert.ok(propertyNames.includes('NameKeyValue')); +assert.ok(!propertyNames.includes('readwriteAccessor1')); +assert.ok(!propertyNames.includes('readwriteAccessor2')); +assert.ok(!propertyNames.includes('readonlyAccessor1')); +assert.ok(!propertyNames.includes('readonlyAccessor2')); + +// Validate property created with symbol +const start = 'Symbol('.length; +const end = start + 'NameKeySymbol'.length; +const symbolDescription = + String(Object.getOwnPropertySymbols(test_object)[0]).slice(start, end); +assert.strictEqual(symbolDescription, 'NameKeySymbol'); + +// The napi_writable attribute should be ignored for accessors. +const readwriteAccessor1Descriptor = + Object.getOwnPropertyDescriptor(test_object, 'readwriteAccessor1'); +const readonlyAccessor1Descriptor = + Object.getOwnPropertyDescriptor(test_object, 'readonlyAccessor1'); +assert.ok(readwriteAccessor1Descriptor.get != null); +assert.ok(readwriteAccessor1Descriptor.set != null); +assert.ok(readwriteAccessor1Descriptor.value === undefined); +assert.ok(readonlyAccessor1Descriptor.get != null); +assert.ok(readonlyAccessor1Descriptor.set === undefined); +assert.ok(readonlyAccessor1Descriptor.value === undefined); +test_object.readwriteAccessor1 = 1; +assert.strictEqual(test_object.readwriteAccessor1, 1); +assert.strictEqual(test_object.readonlyAccessor1, 1); +assert.throws(() => { test_object.readonlyAccessor1 = 3; }, getterOnlyErrorRE); +test_object.readwriteAccessor2 = 2; +assert.strictEqual(test_object.readwriteAccessor2, 2); +assert.strictEqual(test_object.readonlyAccessor2, 2); +assert.throws(() => { test_object.readonlyAccessor2 = 3; }, getterOnlyErrorRE); + +assert.strictEqual(test_object.hasNamedProperty(test_object, 'echo'), true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'hiddenValue'), + true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'doesnotexist'), + false); diff --git a/Tests/NodeApi/test/js-native-api/test_properties/test_properties.c b/Tests/NodeApi/test/js-native-api/test_properties/test_properties.c new file mode 100644 index 00000000..7b8e67a9 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_properties/test_properties.c @@ -0,0 +1,113 @@ +#define NAPI_VERSION 9 +#include +#include "../common.h" +#include "../entry_point.h" + +static double value_ = 1; + +static napi_value GetValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + return number; +} + +static napi_value SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value_)); + + return NULL; +} + +static napi_value Echo(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments"); + + return args[0]; +} + +static napi_value HasNamedProperty(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + // Extract the name of the property to check + char buffer[128]; + size_t copied; + NODE_API_CALL(env, + napi_get_value_string_utf8(env, args[1], buffer, sizeof(buffer), &copied)); + + // do the check and create the boolean return value + bool value; + napi_value result; + NODE_API_CALL(env, napi_has_named_property(env, args[0], buffer, &value)); + NODE_API_CALL(env, napi_get_boolean(env, value, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value number; + NODE_API_CALL(env, napi_create_double(env, value_, &number)); + + napi_value name_value; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "NameKeyValue", NAPI_AUTO_LENGTH, &name_value)); + + napi_value symbol_description; + napi_value name_symbol; + NODE_API_CALL(env, + napi_create_string_utf8( + env, "NameKeySymbol", NAPI_AUTO_LENGTH, &symbol_description)); + NODE_API_CALL(env, + napi_create_symbol(env, symbol_description, &name_symbol)); + + napi_value name_symbol_descriptionless; + NODE_API_CALL(env, + napi_create_symbol(env, NULL, &name_symbol_descriptionless)); + + napi_value name_symbol_for; + NODE_API_CALL(env, node_api_symbol_for(env, + "NameKeySymbolFor", + NAPI_AUTO_LENGTH, + &name_symbol_for)); + + napi_property_descriptor properties[] = { + { "echo", 0, Echo, 0, 0, 0, napi_enumerable, 0 }, + { "readwriteValue", 0, 0, 0, 0, number, napi_enumerable | napi_writable, 0 }, + { "readonlyValue", 0, 0, 0, 0, number, napi_enumerable, 0}, + { "hiddenValue", 0, 0, 0, 0, number, napi_default, 0}, + { NULL, name_value, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol_descriptionless, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol_for, 0, 0, 0, number, napi_enumerable, 0}, + { "readwriteAccessor1", 0, 0, GetValue, SetValue, 0, napi_default, 0}, + { "readwriteAccessor2", 0, 0, GetValue, SetValue, 0, napi_writable, 0}, + { "readonlyAccessor1", 0, 0, GetValue, NULL, 0, napi_default, 0}, + { "readonlyAccessor2", 0, 0, GetValue, NULL, 0, napi_writable, 0}, + { "hasNamedProperty", 0, HasNamedProperty, 0, 0, 0, napi_default, 0 }, + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_reference/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_reference/CMakeLists.txt new file mode 100644 index 00000000..2c0dd7b5 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/CMakeLists.txt @@ -0,0 +1,11 @@ +add_node_api_module(test_reference + SOURCES + test_reference.c + DEFINES + "NAPI_VERSION=9" +) + +add_node_api_module(test_finalizer + SOURCES + test_finalizer.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_reference/binding.gyp b/Tests/NodeApi/test/js-native-api/test_reference/binding.gyp new file mode 100644 index 00000000..2f2acb3a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/binding.gyp @@ -0,0 +1,16 @@ +{ + "targets": [ + { + "target_name": "test_reference", + "sources": [ + "test_reference.c" + ] + }, + { + "target_name": "test_finalizer", + "sources": [ + "test_finalizer.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_reference/test.js b/Tests/NodeApi/test/js-native-api/test_reference/test.js new file mode 100644 index 00000000..aa5c9953 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/test.js @@ -0,0 +1,158 @@ +'use strict'; +// Flags: --expose-gc + +const { buildType } = require('../../common'); +const { gcUntil } = require('../../common/gc'); +const assert = require('assert'); + +const test_reference = require(`./build/${buildType}/test_reference`); + +// This test script uses external values with finalizer callbacks +// in order to track when values get garbage-collected. Each invocation +// of a finalizer callback increments the finalizeCount property. +assert.strictEqual(test_reference.finalizeCount, 0); + +// Run each test function in sequence, +// with an async delay and GC call between each. +async function runTests() { + (() => { + const symbol = test_reference.createSymbol('testSym'); + test_reference.createReference(symbol, 0); + assert.strictEqual(test_reference.referenceValue, symbol); + })(); + test_reference.deleteReference(); + + (() => { + const symbol = test_reference.createSymbolFor('testSymFor'); + test_reference.createReference(symbol, 0); + assert.strictEqual(test_reference.referenceValue, symbol); + })(); + test_reference.deleteReference(); + + (() => { + const symbol = test_reference.createSymbolFor('testSymFor'); + test_reference.createReference(symbol, 1); + assert.strictEqual(test_reference.referenceValue, symbol); + assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor')); + })(); + test_reference.deleteReference(); + + (() => { + const symbol = test_reference.createSymbolForEmptyString(); + test_reference.createReference(symbol, 0); + assert.strictEqual(test_reference.referenceValue, Symbol.for('')); + })(); + test_reference.deleteReference(); + + (() => { + const symbol = test_reference.createSymbolForEmptyString(); + test_reference.createReference(symbol, 1); + assert.strictEqual(test_reference.referenceValue, symbol); + assert.strictEqual(test_reference.referenceValue, Symbol.for('')); + })(); + test_reference.deleteReference(); + + assert.throws(() => test_reference.createSymbolForIncorrectLength(), + /Invalid argument/); + + (() => { + const value = test_reference.createExternal(); + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(typeof value, 'object'); + test_reference.checkExternal(value); + })(); + await gcUntil('External value without a finalizer', + () => (test_reference.finalizeCount === 0)); + + (() => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(typeof value, 'object'); + test_reference.checkExternal(value); + })(); + await gcUntil('External value with a finalizer', + () => (test_reference.finalizeCount === 1)); + + (() => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 0); + assert.strictEqual(test_reference.referenceValue, value); + })(); + // Value should be GC'd because there is only a weak ref + await gcUntil('Weak reference', + () => (test_reference.referenceValue === undefined && + test_reference.finalizeCount === 1)); + test_reference.deleteReference(); + + (() => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 1); + assert.strictEqual(test_reference.referenceValue, value); + })(); + // Value should NOT be GC'd because there is a strong ref + await gcUntil('Strong reference', + () => (test_reference.finalizeCount === 0)); + test_reference.deleteReference(); + await gcUntil('Strong reference (cont.d)', + () => (test_reference.finalizeCount === 1)); + + (() => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 1); + })(); + // Value should NOT be GC'd because there is a strong ref + await gcUntil('Strong reference, increment then decrement to weak reference', + () => (test_reference.finalizeCount === 0)); + assert.strictEqual(test_reference.incrementRefcount(), 2); + // Value should NOT be GC'd because there is a strong ref + await gcUntil( + 'Strong reference, increment then decrement to weak reference (cont.d-1)', + () => (test_reference.finalizeCount === 0)); + assert.strictEqual(test_reference.decrementRefcount(), 1); + // Value should NOT be GC'd because there is a strong ref + await gcUntil( + 'Strong reference, increment then decrement to weak reference (cont.d-2)', + () => (test_reference.finalizeCount === 0)); + assert.strictEqual(test_reference.decrementRefcount(), 0); + // Value should be GC'd because the ref is now weak! + await gcUntil( + 'Strong reference, increment then decrement to weak reference (cont.d-3)', + () => (test_reference.finalizeCount === 1)); + test_reference.deleteReference(); + // Value was already GC'd + await gcUntil( + 'Strong reference, increment then decrement to weak reference (cont.d-4)', + () => (test_reference.finalizeCount === 1)); +} +runTests(); + +// This test creates a napi_ref on an object that has +// been wrapped by napi_wrap and for which the finalizer +// for the wrap calls napi_delete_ref on that napi_ref. +// +// Since both the wrap and the reference use the same +// object the finalizer for the wrap and reference +// may run in the same gc and in any order. +// +// It does that to validate that napi_delete_ref can be +// called before the finalizer has been run for the +// reference (there is a finalizer behind the scenes even +// though it cannot be passed to napi_create_reference). +// +// Since the order is not guaranteed, run the +// test a number of times maximize the chance that we +// get a run with the desired order for the test. +// +// 1000 reliably recreated the problem without the fix +// required to ensure delete could be called before +// the finalizer in manual testing. +for (let i = 0; i < 1000; i++) { + (() => { + const wrapObject = new Object(); + test_reference.validateDeleteBeforeFinalize(wrapObject); + })(); + global.gc(); +} diff --git a/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.c b/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.c new file mode 100644 index 00000000..0ce671b6 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static int test_value = 1; +static int finalize_count = 0; + +static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalize_count, &result)); + return result; +} +static void FinalizeExternalCallJs(napi_env env, void* data, void* hint) { + finalize_count++; + + int* actual_value = data; + NODE_API_ASSERT_RETURN_VOID( + env, + actual_value == &test_value, + "The correct pointer was passed to the finalizer"); + + napi_ref finalizer_ref = (napi_ref)hint; + napi_value js_finalizer; + napi_value recv; + NODE_API_CALL_RETURN_VOID( + env, napi_get_reference_value(env, finalizer_ref, &js_finalizer)); + NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &recv)); + NODE_API_CALL_RETURN_VOID( + env, napi_call_function(env, recv, js_finalizer, 0, NULL, NULL)); + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, finalizer_ref)); +} + +static napi_value CreateExternalWithJsFinalize(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_value finalizer = args[0]; + napi_valuetype finalizer_valuetype; + NODE_API_CALL(env, napi_typeof(env, finalizer, &finalizer_valuetype)); + NODE_API_ASSERT(env, + finalizer_valuetype == napi_function, + "Wrong type of first argument"); + napi_ref finalizer_ref; + NODE_API_CALL(env, napi_create_reference(env, finalizer, 1, &finalizer_ref)); + + napi_value result; + NODE_API_CALL(env, + napi_create_external(env, + &test_value, + FinalizeExternalCallJs, + finalizer_ref, /* finalize_hint */ + &result)); + + finalize_count = 0; + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_GETTER("finalizeCount", GetFinalizeCount), + DECLARE_NODE_API_PROPERTY("createExternalWithJsFinalize", + CreateExternalWithJsFinalize), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.js b/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.js new file mode 100644 index 00000000..0b973163 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/test_finalizer.js @@ -0,0 +1,24 @@ +'use strict'; +// Flags: --expose-gc --force-node-api-uncaught-exceptions-policy + +const common = require('../../common'); +const binding = require(`./build/${common.buildType}/test_finalizer`); +const assert = require('assert'); +const { gcUntil } = require('../../common/gc'); + +process.on('uncaughtException', common.mustCall((err) => { + assert.throws(() => { throw err; }, /finalizer error/); +})); + +(async function() { + (() => { + binding.createExternalWithJsFinalize( + common.mustCall(() => { + throw new Error('finalizer error'); + }) + ); + })(); + await gcUntil('External value calls finalizer', + () => (binding.finalizeCount === 1)); + +})().then(common.mustCall()); diff --git a/Tests/NodeApi/test/js-native-api/test_reference/test_reference.c b/Tests/NodeApi/test/js-native-api/test_reference/test_reference.c new file mode 100644 index 00000000..a66b8068 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference/test_reference.c @@ -0,0 +1,252 @@ +#define NAPI_VERSION 9 +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static int test_value = 1; +static int finalize_count = 0; +static napi_ref test_reference = NULL; + +static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalize_count, &result)); + return result; +} + +static void FinalizeExternal(napi_env env, void* data, void* hint) { + int *actual_value = data; + NODE_API_ASSERT_RETURN_VOID(env, actual_value == &test_value, + "The correct pointer was passed to the finalizer"); + finalize_count++; +} + +static napi_value CreateExternal(napi_env env, napi_callback_info info) { + int* data = &test_value; + + napi_value result; + NODE_API_CALL(env, + napi_create_external(env, + data, + NULL, /* finalize_cb */ + NULL, /* finalize_hint */ + &result)); + + finalize_count = 0; + return result; +} + +static napi_value CreateSymbol(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, "Expect one argument only (symbol description)"); + + napi_value result_symbol; + + NODE_API_CALL(env, napi_create_symbol(env, args[0], &result_symbol)); + return result_symbol; +} + +static napi_value CreateSymbolFor(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + + char description[256]; + size_t description_length; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT( + env, argc == 1, "Expect one argument only (symbol description)"); + + NODE_API_CALL( + env, + napi_get_value_string_utf8( + env, args[0], description, sizeof(description), &description_length)); + NODE_API_ASSERT(env, + description_length <= 255, + "Cannot accommodate descriptions longer than 255 bytes"); + + napi_value result_symbol; + + NODE_API_CALL(env, + node_api_symbol_for( + env, description, description_length, &result_symbol)); + return result_symbol; +} + +static napi_value CreateSymbolForEmptyString(napi_env env, napi_callback_info info) { + napi_value result_symbol; + NODE_API_CALL(env, node_api_symbol_for(env, NULL, 0, &result_symbol)); + return result_symbol; +} + +static napi_value CreateSymbolForIncorrectLength(napi_env env, napi_callback_info info) { + napi_value result_symbol; + NODE_API_CALL(env, node_api_symbol_for(env, NULL, 5, &result_symbol)); + return result_symbol; +} + +static napi_value +CreateExternalWithFinalize(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, + napi_create_external(env, + &test_value, + FinalizeExternal, + NULL, /* finalize_hint */ + &result)); + + finalize_count = 0; + return result; +} + +static napi_value CheckExternal(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 1, "Expected one argument."); + + napi_valuetype argtype; + NODE_API_CALL(env, napi_typeof(env, arg, &argtype)); + + NODE_API_ASSERT(env, argtype == napi_external, "Expected an external value."); + + void* data; + NODE_API_CALL(env, napi_get_value_external(env, arg, &data)); + + NODE_API_ASSERT(env, data != NULL && *(int*)data == test_value, + "An external data value of 1 was expected."); + + return NULL; +} + +static napi_value CreateReference(napi_env env, napi_callback_info info) { + NODE_API_ASSERT(env, test_reference == NULL, + "The test allows only one reference at a time."); + + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NODE_API_ASSERT(env, argc == 2, "Expected two arguments."); + + uint32_t initial_refcount; + NODE_API_CALL(env, napi_get_value_uint32(env, args[1], &initial_refcount)); + + NODE_API_CALL(env, + napi_create_reference(env, args[0], initial_refcount, &test_reference)); + + NODE_API_ASSERT(env, test_reference != NULL, + "A reference should have been created."); + + return NULL; +} + +static napi_value DeleteReference(napi_env env, napi_callback_info info) { + NODE_API_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + NODE_API_CALL(env, napi_delete_reference(env, test_reference)); + test_reference = NULL; + return NULL; +} + +static napi_value IncrementRefcount(napi_env env, napi_callback_info info) { + NODE_API_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + uint32_t refcount; + NODE_API_CALL(env, napi_reference_ref(env, test_reference, &refcount)); + + napi_value result; + NODE_API_CALL(env, napi_create_uint32(env, refcount, &result)); + return result; +} + +static napi_value DecrementRefcount(napi_env env, napi_callback_info info) { + NODE_API_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + uint32_t refcount; + NODE_API_CALL(env, napi_reference_unref(env, test_reference, &refcount)); + + napi_value result; + NODE_API_CALL(env, napi_create_uint32(env, refcount, &result)); + return result; +} + +static napi_value GetReferenceValue(napi_env env, napi_callback_info info) { + NODE_API_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + napi_value result; + NODE_API_CALL(env, napi_get_reference_value(env, test_reference, &result)); + return result; +} + +static void DeleteBeforeFinalizeFinalizer( + napi_env env, void* finalize_data, void* finalize_hint) { + napi_ref* ref = (napi_ref*)finalize_data; + napi_value value; + assert(napi_get_reference_value(env, *ref, &value) == napi_ok); + assert(value == NULL); + napi_delete_reference(env, *ref); + free(ref); +} + +static napi_value ValidateDeleteBeforeFinalize(napi_env env, napi_callback_info info) { + napi_value wrapObject; + size_t argc = 1; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &wrapObject, NULL, NULL)); + + napi_ref* ref_t = malloc(sizeof(napi_ref)); + NODE_API_CALL(env, + napi_wrap( + env, wrapObject, ref_t, DeleteBeforeFinalizeFinalizer, NULL, NULL)); + + // Create a reference that will be eligible for collection at the same + // time as the wrapped object by passing in the same wrapObject. + // This means that the FinalizeOrderValidation callback may be run + // before the finalizer for the newly created reference (there is a finalizer + // behind the scenes even though it cannot be passed to napi_create_reference) + // The Finalizer for the wrap (which is different than the finalizer + // for the reference) calls napi_delete_reference validating that + // napi_delete_reference can be called before the finalizer for the + // reference runs. + NODE_API_CALL(env, napi_create_reference(env, wrapObject, 0, ref_t)); + return wrapObject; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_GETTER("finalizeCount", GetFinalizeCount), + DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal), + DECLARE_NODE_API_PROPERTY("createExternalWithFinalize", + CreateExternalWithFinalize), + DECLARE_NODE_API_PROPERTY("checkExternal", CheckExternal), + DECLARE_NODE_API_PROPERTY("createReference", CreateReference), + DECLARE_NODE_API_PROPERTY("createSymbol", CreateSymbol), + DECLARE_NODE_API_PROPERTY("createSymbolFor", CreateSymbolFor), + DECLARE_NODE_API_PROPERTY("createSymbolForEmptyString", + CreateSymbolForEmptyString), + DECLARE_NODE_API_PROPERTY("createSymbolForIncorrectLength", + CreateSymbolForIncorrectLength), + DECLARE_NODE_API_PROPERTY("deleteReference", DeleteReference), + DECLARE_NODE_API_PROPERTY("incrementRefcount", IncrementRefcount), + DECLARE_NODE_API_PROPERTY("decrementRefcount", DecrementRefcount), + DECLARE_NODE_API_GETTER("referenceValue", GetReferenceValue), + DECLARE_NODE_API_PROPERTY("validateDeleteBeforeFinalize", + ValidateDeleteBeforeFinalize), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_reference_double_free/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_reference_double_free/CMakeLists.txt new file mode 100644 index 00000000..3fe097a0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference_double_free/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_reference_double_free + SOURCES + test_reference_double_free.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_reference_double_free/binding.gyp b/Tests/NodeApi/test/js-native-api/test_reference_double_free/binding.gyp new file mode 100644 index 00000000..8e49ef22 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference_double_free/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_reference_double_free", + "sources": [ + "test_reference_double_free.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_reference_double_free/test.js b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test.js new file mode 100644 index 00000000..f9a465c5 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test.js @@ -0,0 +1,11 @@ +'use strict'; + +// This test makes no assertions. It tests a fix without which it will crash +// with a double free. + +const { buildType } = require('../../common'); + +const addon = require(`./build/${buildType}/test_reference_double_free`); + +{ new addon.MyObject(true); } +{ new addon.MyObject(false); } diff --git a/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_reference_double_free.c b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_reference_double_free.c new file mode 100644 index 00000000..e99667a7 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_reference_double_free.c @@ -0,0 +1,90 @@ +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static size_t g_call_count = 0; + +static void Destructor(napi_env env, void* data, void* nothing) { + napi_ref* ref = data; + NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref)); + free(ref); +} + +static void NoDeleteDestructor(napi_env env, void* data, void* hint) { + napi_ref* ref = data; + size_t* call_count = hint; + + // This destructor must be called exactly once. + if ((*call_count) > 0) abort(); + *call_count = ((*call_count) + 1); + free(ref); +} + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_this, js_delete; + bool delete; + napi_ref* ref = malloc(sizeof(*ref)); + + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, &js_delete, &js_this, NULL)); + NODE_API_CALL(env, napi_get_value_bool(env, js_delete, &delete)); + + if (delete) { + NODE_API_CALL(env, + napi_wrap(env, js_this, ref, Destructor, NULL, ref)); + } else { + NODE_API_CALL(env, + napi_wrap(env, js_this, ref, NoDeleteDestructor, &g_call_count, ref)); + } + NODE_API_CALL(env, napi_reference_ref(env, *ref, NULL)); + + return js_this; +} + +static void NoopDeleter(napi_env env, void* data, void* hint) {} + +// Tests that calling napi_remove_wrap and napi_delete_reference consecutively +// doesn't crash the process. +// This is analogous to the test https://github.com/nodejs/node-addon-api/blob/main/test/objectwrap_constructor_exception.cc. +// In which the Napi::ObjectWrap<> is being destructed immediately after napi_wrap. +// As Napi::ObjectWrap<> is a subclass of Napi::Reference<>, napi_remove_wrap +// in the destructor of Napi::ObjectWrap<> is called before napi_delete_reference +// in the destructor of Napi::Reference<>. +static napi_value DeleteImmediately(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_obj; + napi_ref ref; + napi_valuetype type; + + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &js_obj, NULL, NULL)); + + NODE_API_CALL(env, napi_typeof(env, js_obj, &type)); + NODE_API_ASSERT(env, type == napi_object, "Expected object parameter"); + + NODE_API_CALL(env, napi_wrap(env, js_obj, NULL, NoopDeleter, NULL, &ref)); + NODE_API_CALL(env, napi_remove_wrap(env, js_obj, NULL)); + NODE_API_CALL(env, napi_delete_reference(env, ref)); + + return NULL; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value myobj_ctor; + NODE_API_CALL(env, + napi_define_class( + env, "MyObject", NAPI_AUTO_LENGTH, New, NULL, 0, NULL, &myobj_ctor)); + NODE_API_CALL(env, + napi_set_named_property(env, exports, "MyObject", myobj_ctor)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("deleteImmediately", DeleteImmediately), + }; + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_wrap.js b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_wrap.js new file mode 100644 index 00000000..f7f75094 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_reference_double_free/test_wrap.js @@ -0,0 +1,10 @@ +'use strict'; + +// This test makes no assertions. It tests that calling napi_remove_wrap and +// napi_delete_reference consecutively doesn't crash the process. + +const { buildType } = require('../../common'); + +const addon = require(`./build/${buildType}/test_reference_double_free`); + +addon.deleteImmediately({}); diff --git a/Tests/NodeApi/test/js-native-api/test_string/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_string/CMakeLists.txt new file mode 100644 index 00000000..5244fdba --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_string/CMakeLists.txt @@ -0,0 +1,7 @@ +add_node_api_module(test_string + SOURCES + test_string.c + test_null.c + DEFINES + "NAPI_VERSION=10" +) diff --git a/Tests/NodeApi/test/js-native-api/test_string/binding.gyp b/Tests/NodeApi/test/js-native-api/test_string/binding.gyp new file mode 100644 index 00000000..550e33b4 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_string/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "test_string", + "sources": [ + "test_string.c", + "test_null.c", + ], + "defines": [ + "NAPI_VERSION=10", + ], + }, + ], +} diff --git a/Tests/NodeApi/test/js-native-api/test_string/test.js b/Tests/NodeApi/test/js-native-api/test_string/test.js new file mode 100644 index 00000000..5d03ba9d --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_string/test.js @@ -0,0 +1,91 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for string +const test_string = require(`./build/${common.buildType}/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/NodeApi/test/js-native-api/test_string/test_null.c b/Tests/NodeApi/test/js-native-api/test_string/test_null.c new file mode 100644 index 00000000..84c1fc40 --- /dev/null +++ b/Tests/NodeApi/test/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/NodeApi/test/js-native-api/test_string/test_null.h b/Tests/NodeApi/test/js-native-api/test_string/test_null.h new file mode 100644 index 00000000..95be6359 --- /dev/null +++ b/Tests/NodeApi/test/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/NodeApi/test/js-native-api/test_string/test_null.js b/Tests/NodeApi/test/js-native-api/test_string/test_null.js new file mode 100644 index 00000000..71963009 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_string/test_null.js @@ -0,0 +1,17 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Test passing NULL to object-related N-APIs. +const { testNull } = require(`./build/${common.buildType}/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/NodeApi/test/js-native-api/test_string/test_string.c b/Tests/NodeApi/test/js-native-api/test_string/test_string.c new file mode 100644 index 00000000..c6874dc7 --- /dev/null +++ b/Tests/NodeApi/test/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 diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_symbol/CMakeLists.txt new file mode 100644 index 00000000..7e937424 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_symbol + SOURCES + test_symbol.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/binding.gyp b/Tests/NodeApi/test/js-native-api/test_symbol/binding.gyp new file mode 100644 index 00000000..6a5a7cad --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_symbol", + "sources": [ + "test_symbol.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/test1.js b/Tests/NodeApi/test/js-native-api/test_symbol/test1.js new file mode 100644 index 00000000..3a28437a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/test1.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +const sym = test_symbol.New('test'); +assert.strictEqual(sym.toString(), 'Symbol(test)'); + +const myObj = {}; +const fooSym = test_symbol.New('foo'); +const otherSym = test_symbol.New('bar'); +myObj.foo = 'bar'; +myObj[fooSym] = 'baz'; +myObj[otherSym] = 'bing'; +assert.strictEqual(myObj.foo, 'bar'); +assert.strictEqual(myObj[fooSym], 'baz'); +assert.strictEqual(myObj[otherSym], 'bing'); diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/test2.js b/Tests/NodeApi/test/js-native-api/test_symbol/test2.js new file mode 100644 index 00000000..026f2c68 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/test2.js @@ -0,0 +1,17 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +const fooSym = test_symbol.New('foo'); +assert.strictEqual(fooSym.toString(), 'Symbol(foo)'); + +const myObj = {}; +myObj.foo = 'bar'; +myObj[fooSym] = 'baz'; + +assert.deepStrictEqual(Object.keys(myObj), ['foo']); +assert.deepStrictEqual(Object.getOwnPropertyNames(myObj), ['foo']); +assert.deepStrictEqual(Object.getOwnPropertySymbols(myObj), [fooSym]); diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/test3.js b/Tests/NodeApi/test/js-native-api/test_symbol/test3.js new file mode 100644 index 00000000..186c561e --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/test3.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +assert.notStrictEqual(test_symbol.New(), test_symbol.New()); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('foo')); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('bar')); + +const foo1 = test_symbol.New('foo'); +const foo2 = test_symbol.New('foo'); +const object = { + [foo1]: 1, + [foo2]: 2, +}; +assert.strictEqual(object[foo1], 1); +assert.strictEqual(object[foo2], 2); diff --git a/Tests/NodeApi/test/js-native-api/test_symbol/test_symbol.c b/Tests/NodeApi/test/js-native-api/test_symbol/test_symbol.c new file mode 100644 index 00000000..58fcb85a --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_symbol/test_symbol.c @@ -0,0 +1,38 @@ +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value description = NULL; + if (argc >= 1) { + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NODE_API_ASSERT(env, valuetype == napi_string, + "Wrong type of arguments. Expects a string."); + + description = args[0]; + } + + napi_value symbol; + NODE_API_CALL(env, napi_create_symbol(env, description, &symbol)); + + return symbol; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("New", New), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/js-native-api/test_typedarray/CMakeLists.txt b/Tests/NodeApi/test/js-native-api/test_typedarray/CMakeLists.txt new file mode 100644 index 00000000..093e92d0 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_typedarray/CMakeLists.txt @@ -0,0 +1,4 @@ +add_node_api_module(test_typedarray + SOURCES + test_typedarray.c +) diff --git a/Tests/NodeApi/test/js-native-api/test_typedarray/binding.gyp b/Tests/NodeApi/test/js-native-api/test_typedarray/binding.gyp new file mode 100644 index 00000000..a5ae5741 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_typedarray/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_typedarray", + "sources": [ + "test_typedarray.c" + ] + } + ] +} diff --git a/Tests/NodeApi/test/js-native-api/test_typedarray/test.js b/Tests/NodeApi/test/js-native-api/test_typedarray/test.js new file mode 100644 index 00000000..673bb5ce --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_typedarray/test.js @@ -0,0 +1,109 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_typedarray = require(`./build/${common.buildType}/test_typedarray`); + +const byteArray = new Uint8Array(3); +byteArray[0] = 0; +byteArray[1] = 1; +byteArray[2] = 2; +assert.strictEqual(byteArray.length, 3); + +const doubleArray = new Float64Array(3); +doubleArray[0] = 0.0; +doubleArray[1] = 1.1; +doubleArray[2] = 2.2; +assert.strictEqual(doubleArray.length, 3); + +const byteResult = test_typedarray.Multiply(byteArray, 3); +assert.ok(byteResult instanceof Uint8Array); +assert.strictEqual(byteResult.length, 3); +assert.strictEqual(byteResult[0], 0); +assert.strictEqual(byteResult[1], 3); +assert.strictEqual(byteResult[2], 6); + +const doubleResult = test_typedarray.Multiply(doubleArray, -3); +assert.ok(doubleResult instanceof Float64Array); +assert.strictEqual(doubleResult.length, 3); +assert.strictEqual(doubleResult[0], -0); +assert.strictEqual(Math.round(10 * doubleResult[1]) / 10, -3.3); +assert.strictEqual(Math.round(10 * doubleResult[2]) / 10, -6.6); + +const externalResult = test_typedarray.External(); +assert.ok(externalResult instanceof Int8Array); +assert.strictEqual(externalResult.length, 3); +assert.strictEqual(externalResult[0], 0); +assert.strictEqual(externalResult[1], 1); +assert.strictEqual(externalResult[2], 2); + +// Validate creation of all kinds of TypedArrays +const buffer = new ArrayBuffer(128); +const arrayTypes = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, + Uint16Array, Int32Array, Uint32Array, Float32Array, + Float64Array, BigInt64Array, BigUint64Array ]; + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + const theArray = test_typedarray.CreateTypedArray(template, buffer); + + assert.ok(theArray instanceof currentType, + 'Type of new array should match that of the template. ' + + `Expected type: ${currentType.name}, ` + + `actual type: ${template.constructor.name}`); + assert.notStrictEqual(theArray, template); + assert.strictEqual(theArray.buffer, buffer); +}); + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, 0, 136); + }, RangeError); +}); + +const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array, + Float32Array, Float64Array, + BigInt64Array, BigUint64Array ]; +nonByteArrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, + currentType.BYTES_PER_ELEMENT + 1, 1); + console.log(`start of offset ${currentType}`); + }, RangeError); +}); + +// Test detaching +arrayTypes.forEach((currentType) => { + const buffer = Reflect.construct(currentType, [8]); + assert.strictEqual(buffer.length, 8); + assert.ok(!test_typedarray.IsDetached(buffer.buffer)); + test_typedarray.Detach(buffer); + assert.ok(test_typedarray.IsDetached(buffer.buffer)); + assert.strictEqual(buffer.length, 0); +}); +{ + const buffer = test_typedarray.External(); + assert.ok(externalResult instanceof Int8Array); + assert.strictEqual(externalResult.length, 3); + assert.strictEqual(externalResult.byteLength, 3); + assert.ok(!test_typedarray.IsDetached(buffer.buffer)); + test_typedarray.Detach(buffer); + assert.ok(test_typedarray.IsDetached(buffer.buffer)); + assert.ok(externalResult instanceof Int8Array); + assert.strictEqual(buffer.length, 0); + assert.strictEqual(buffer.byteLength, 0); +} + +{ + const buffer = new ArrayBuffer(128); + assert.ok(!test_typedarray.IsDetached(buffer)); +} + +{ + const buffer = test_typedarray.NullArrayBuffer(); + assert.ok(buffer instanceof ArrayBuffer); + assert.ok(test_typedarray.IsDetached(buffer)); +} diff --git a/Tests/NodeApi/test/js-native-api/test_typedarray/test_typedarray.c b/Tests/NodeApi/test/js-native-api/test_typedarray/test_typedarray.c new file mode 100644 index 00000000..8aac9b52 --- /dev/null +++ b/Tests/NodeApi/test/js-native-api/test_typedarray/test_typedarray.c @@ -0,0 +1,249 @@ +#include +#include +#include +#include "../common.h" +#include "../entry_point.h" + +static napi_value Multiply(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_value input_array = args[0]; + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NODE_API_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + double multiplier; + NODE_API_CALL(env, napi_get_value_double(env, args[1], &multiplier)); + + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset; + size_t i, length; + NODE_API_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &input_buffer, &byte_offset)); + + void* data; + size_t byte_length; + NODE_API_CALL(env, napi_get_arraybuffer_info( + env, input_buffer, &data, &byte_length)); + + napi_value output_buffer; + void* output_ptr = NULL; + NODE_API_CALL(env, napi_create_arraybuffer( + env, byte_length, &output_ptr, &output_buffer)); + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray( + env, type, length, output_buffer, byte_offset, &output_array)); + + if (type == napi_uint8_array) { + uint8_t* input_bytes = (uint8_t*)(data) + byte_offset; + uint8_t* output_bytes = (uint8_t*)(output_ptr); + for (i = 0; i < length; i++) { + output_bytes[i] = (uint8_t)(input_bytes[i] * multiplier); + } + } else if (type == napi_float64_array) { + double* input_doubles = (double*)((uint8_t*)(data) + byte_offset); + double* output_doubles = (double*)(output_ptr); + for (i = 0; i < length; i++) { + output_doubles[i] = input_doubles[i] * multiplier; + } + } else { + napi_throw_error(env, NULL, + "Typed array was of a type not expected by test."); + return NULL; + } + + return output_array; +} + +static void FinalizeCallback(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) +{ + free(finalize_data); +} + +static napi_value External(napi_env env, napi_callback_info info) { + const uint8_t nElem = 3; + int8_t* externalData = malloc(nElem*sizeof(int8_t)); + externalData[0] = 0; + externalData[1] = 1; + externalData[2] = 2; + + napi_value output_buffer; + NODE_API_CALL(env, napi_create_external_arraybuffer( + env, + externalData, + nElem*sizeof(int8_t), + FinalizeCallback, + NULL, // finalize_hint + &output_buffer)); + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray(env, + napi_int8_array, + nElem, + output_buffer, + 0, + &output_array)); + + return output_array; +} + + +static napi_value NullArrayBuffer(napi_env env, napi_callback_info info) { + static void* data = NULL; + napi_value arraybuffer; + NODE_API_CALL(env, + napi_create_external_arraybuffer(env, data, 0, NULL, NULL, &arraybuffer)); + return arraybuffer; +} + +static napi_value CreateTypedArray(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value args[4]; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NODE_API_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments"); + + napi_value input_array = args[0]; + napi_valuetype valuetype0; + NODE_API_CALL(env, napi_typeof(env, input_array, &valuetype0)); + + NODE_API_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + bool is_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NODE_API_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + napi_value input_buffer = args[1]; + NODE_API_CALL(env, napi_typeof(env, input_buffer, &valuetype1)); + + NODE_API_ASSERT(env, valuetype1 == napi_object, + "Wrong type of arguments. Expects an array buffer as second argument."); + + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, input_buffer, &is_arraybuffer)); + + NODE_API_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects an array buffer as second argument."); + + napi_typedarray_type type; + napi_value in_array_buffer; + size_t byte_offset; + size_t length; + NODE_API_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &in_array_buffer, &byte_offset)); + + if (argc == 4) { + napi_valuetype valuetype2; + NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NODE_API_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_length; + NODE_API_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length)); + length = uint32_length; + + napi_valuetype valuetype3; + NODE_API_CALL(env, napi_typeof(env, args[3], &valuetype3)); + + NODE_API_ASSERT(env, valuetype3 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_byte_offset; + NODE_API_CALL(env, napi_get_value_uint32(env, args[3], &uint32_byte_offset)); + byte_offset = uint32_byte_offset; + } + + napi_value output_array; + NODE_API_CALL(env, napi_create_typedarray( + env, type, length, input_buffer, byte_offset, &output_array)); + + return output_array; +} + +static napi_value Detach(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_typedarray; + NODE_API_CALL(env, napi_is_typedarray(env, args[0], &is_typedarray)); + NODE_API_ASSERT( + env, is_typedarray, + "Wrong type of arguments. Expects a typedarray as first argument."); + + napi_value arraybuffer; + NODE_API_CALL(env, + napi_get_typedarray_info( + env, args[0], NULL, NULL, NULL, &arraybuffer, NULL)); + NODE_API_CALL(env, napi_detach_arraybuffer(env, arraybuffer)); + + return NULL; +} + +static napi_value IsDetached(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_value array_buffer = args[0]; + bool is_arraybuffer; + NODE_API_CALL(env, napi_is_arraybuffer(env, array_buffer, &is_arraybuffer)); + NODE_API_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects an array buffer as first argument."); + + bool is_detached; + NODE_API_CALL(env, + napi_is_detached_arraybuffer(env, array_buffer, &is_detached)); + + napi_value result; + NODE_API_CALL(env, napi_get_boolean(env, is_detached, &result)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("Multiply", Multiply), + DECLARE_NODE_API_PROPERTY("External", External), + DECLARE_NODE_API_PROPERTY("NullArrayBuffer", NullArrayBuffer), + DECLARE_NODE_API_PROPERTY("CreateTypedArray", CreateTypedArray), + DECLARE_NODE_API_PROPERTY("Detach", Detach), + DECLARE_NODE_API_PROPERTY("IsDetached", IsDetached), + }; + + NODE_API_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/Tests/NodeApi/test/package.json b/Tests/NodeApi/test/package.json new file mode 100644 index 00000000..21023796 --- /dev/null +++ b/Tests/NodeApi/test/package.json @@ -0,0 +1,14 @@ +{ + "name": "hermes-node-api-test-packages", + "private": true, + "scripts": { + "postinstall": "tar -c -f node_modules.tar node_modules & npx hasha -a sha1 node_modules.tar > node_modules.sha1" + }, + "devDependencies": { + "@babel/cli": "^7.28.0", + "@babel/core": "^7.28.0", + "@babel/runtime": "^7.28.2", + "@react-native/babel-preset": "^0.80.2", + "hasha-cli": "^6.0.0" + } +} diff --git a/Tests/NodeApi/test/yarn.lock b/Tests/NodeApi/test/yarn.lock new file mode 100644 index 00000000..b5fb0864 --- /dev/null +++ b/Tests/NodeApi/test/yarn.lock @@ -0,0 +1,1326 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/cli@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.28.0.tgz#26959456cbedff569a2c3ac909e8a268ca6cb7e2" + integrity sha512-CYrZG7FagtE8ReKDBfItxnrEBf2khq2eTMnPuqO8UVN0wzhp1eMX1wfda8b1a32l2aqYLwRRIOGNovm8FVzmMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.28" + commander "^6.2.0" + convert-source-map "^2.0.0" + fs-readdir-recursive "^1.1.0" + glob "^7.2.0" + make-dir "^2.1.0" + slash "^2.0.0" + optionalDependencies: + "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" + chokidar "^3.6.0" + +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.25.2", "@babel/core@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" + integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.27.3" + "@babel/helpers" "^7.27.6" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.0" + "@babel/types" "^7.28.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" + integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== + dependencies: + "@babel/parser" "^7.28.0" + "@babel/types" "^7.28.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281" + integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.27.1" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz#05b0882d97ba1d4d03519e4bce615d70afa18c53" + integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + regexpu-core "^6.2.0" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" + integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== + dependencies: + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + debug "^4.4.1" + lodash.debounce "^4.0.8" + resolve "^1.22.10" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-member-expression-to-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" + integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" + integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.3" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz#b88285009c31427af318d4fe37651cd62a142409" + integrity sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helpers@^7.27.6": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.2.tgz#80f0918fecbfebea9af856c419763230040ee850" + integrity sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + +"@babel/parser@^7.27.2", "@babel/parser@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" + integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== + dependencies: + "@babel/types" "^7.28.0" + +"@babel/plugin-proposal-export-default-from@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz#59b050b0e5fdc366162ab01af4fcbac06ea40919" + integrity sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.27.1.tgz#8efed172e79ab657c7fa4d599224798212fb7e18" + integrity sha512-eBC/3KSekshx19+N40MzjWqJd7KTEdOoLesAfa4IDFI8eRz5a47i5Oszus6zG/cwIXN63YhgLOMSSNJx49sENg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-flow@^7.12.1", "@babel/plugin-syntax-flow@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz#6c83cf0d7d635b716827284b7ecd5aead9237662" + integrity sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-async-generator-functions@^7.25.4": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" + integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.28.0" + +"@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" + integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + +"@babel/plugin-transform-block-scoping@^7.25.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz#e7c50cbacc18034f210b93defa89638666099451" + integrity sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-class-properties@^7.25.4": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" + integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-classes@^7.25.4": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz#12fa46cffc32a6e084011b650539e880add8a0f8" + integrity sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/traverse" "^7.28.0" + +"@babel/plugin-transform-computed-properties@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" + integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/template" "^7.27.1" + +"@babel/plugin-transform-destructuring@^7.24.8", "@babel/plugin-transform-destructuring@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz#0f156588f69c596089b7d5b06f5af83d9aa7f97a" + integrity sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.0" + +"@babel/plugin-transform-flow-strip-types@^7.25.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz#5def3e1e7730f008d683144fb79b724f92c5cdf9" + integrity sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-flow" "^7.27.1" + +"@babel/plugin-transform-for-of@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-function-name@^7.25.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== + dependencies: + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-literals@^7.25.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz#890cb20e0270e0e5bebe3f025b434841c32d5baa" + integrity sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-commonjs@^7.24.8": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" + integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" + integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" + integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" + integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz#d23021857ffd7cd809f54d624299b8086402ed8d" + integrity sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA== + dependencies: + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.0" + +"@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" + integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz#874ce3c4f06b7780592e946026eb76a32830454f" + integrity sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-parameters@^7.24.7", "@babel/plugin-transform-parameters@^7.27.7": + version "7.27.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" + integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-methods@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" + integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-display-name@^7.24.7": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz#6f20a7295fea7df42eb42fed8f896813f5b934de" + integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-self@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx@^7.25.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" + integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/plugin-transform-regenerator@^7.24.7": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz#bde80603442ff4bb4e910bc8b35485295d556ab1" + integrity sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-runtime@^7.24.7": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.0.tgz#462e79008cc7bdac03e4c5e1765b9de2bcd31c21" + integrity sha512-dGopk9nZrtCs2+nfIem25UuHyt5moSJamArzIoh9/vezUQPmYDOzjaHDCkAzuGJibCIkPup8rMT2+wYB6S73cA== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + babel-plugin-polyfill-corejs2 "^0.4.14" + babel-plugin-polyfill-corejs3 "^0.13.0" + babel-plugin-polyfill-regenerator "^0.6.5" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-spread@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" + integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typescript@^7.25.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz#796cbd249ab56c18168b49e3e1d341b72af04a6b" + integrity sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + +"@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/runtime@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473" + integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA== + +"@babel/template@^7.25.0", "@babel/template@^7.27.1", "@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.0" + debug "^4.3.1" + +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.0", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" + integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" + integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" + integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": + version "2.1.8-no-fsevents.3" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" + integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== + +"@react-native/babel-plugin-codegen@0.80.2": + version "0.80.2" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.80.2.tgz#14964a7c7058e25df60b41b4dd6b8a3714a33440" + integrity sha512-q0XzdrdDebPwt5tEi2MSo90kpEcs4e3ZZskrbxda081DEjHhgm3bbIxAiW3BxY6adOf/eXxgOhKEGWTfG2me6g== + dependencies: + "@babel/traverse" "^7.25.3" + "@react-native/codegen" "0.80.2" + +"@react-native/babel-preset@^0.80.2": + version "0.80.2" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.80.2.tgz#8c5ba82a37c044c22cf92e613eb8be01310782e5" + integrity sha512-vLtS8YJV0nAnOZ8kVJBaXzHlwvoMXpYB4/NBR1BuAesE+WTiAkXpDFnKSkXBHoS03d/5HYNVcW8VRaB2f0Jmtw== + dependencies: + "@babel/core" "^7.25.2" + "@babel/plugin-proposal-export-default-from" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-default-from" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.4" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.25.0" + "@babel/plugin-transform-class-properties" "^7.25.4" + "@babel/plugin-transform-classes" "^7.25.4" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" + "@babel/plugin-transform-flow-strip-types" "^7.25.2" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.25.1" + "@babel/plugin-transform-literals" "^7.25.2" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-react-display-name" "^7.24.7" + "@babel/plugin-transform-react-jsx" "^7.25.2" + "@babel/plugin-transform-react-jsx-self" "^7.24.7" + "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-runtime" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-typescript" "^7.25.2" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/template" "^7.25.0" + "@react-native/babel-plugin-codegen" "0.80.2" + babel-plugin-syntax-hermes-parser "0.28.1" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.14.0" + +"@react-native/codegen@0.80.2": + version "0.80.2" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.80.2.tgz#2e5dc975400d41b84c7393d73cfe32f47b12d82e" + integrity sha512-eYad9ex9/RS6oFbbpu6LxsczktbhfJbJlTvtRlcWLJjJbFTeNr5Q7CgBT2/m5VtpxnJ/0YdmZ9vdazsJ2yp9kw== + dependencies: + glob "^7.1.1" + hermes-parser "0.28.1" + invariant "^2.2.4" + nullthrows "^1.1.1" + yargs "^17.6.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +babel-plugin-polyfill-corejs2@^0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" + integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== + dependencies: + "@babel/compat-data" "^7.27.7" + "@babel/helper-define-polyfill-provider" "^0.6.5" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" + integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.5" + core-js-compat "^3.43.0" + +babel-plugin-polyfill-regenerator@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" + integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.5" + +babel-plugin-syntax-hermes-parser@0.28.1: + version "0.28.1" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.28.1.tgz#9e80a774ddb8038307a62316486669c668fb3568" + integrity sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ== + dependencies: + hermes-parser "0.28.1" + +babel-plugin-transform-flow-enums@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz#d1d0cc9bdc799c850ca110d0ddc9f21b9ec3ef25" + integrity sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ== + dependencies: + "@babel/plugin-syntax-flow" "^7.12.1" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0, browserslist@^4.25.1: + version "4.25.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" + integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== + dependencies: + caniuse-lite "^1.0.30001726" + electron-to-chromium "^1.5.173" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +caniuse-lite@^1.0.30001726: + version "1.0.30001727" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" + integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== + +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.43.0: + version "3.44.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.44.0.tgz#62b9165b97e4cbdb8bca16b14818e67428b4a0f8" + integrity sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA== + dependencies: + browserslist "^4.25.1" + +debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +electron-to-chromium@^1.5.173: + version "1.5.192" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz#6dfc57a41846a57b18f9c0121821a6df1e165cc1" + integrity sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.1, glob@^7.2.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +hasha-cli@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hasha-cli/-/hasha-cli-6.0.0.tgz#f39fb9fae2a9cfaab6755b690c22e26d67e107ce" + integrity sha512-T5NbVb/ksS9aFTcWijOoQnsqLPBGfdjw+ii4QVKWKaCWTYsxBtbuXlsQBy+igndxLDjESjrlK7l+hSO67aIL3Q== + dependencies: + hasha "^6.0.0" + meow "^12.1.1" + +hasha@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-6.0.0.tgz#bdf1231ae40b406121c09c13705e5b38c1bb607c" + integrity sha512-MLydoyGp9QJcjlhE5lsLHXYpWayjjWqkavzju2ZWD2tYa1CgmML1K1gWAu22BLFa2eZ0OfvJ/DlfoVjaD54U2Q== + dependencies: + is-stream "^3.0.0" + type-fest "^4.7.1" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hermes-estree@0.28.1: + version "0.28.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.28.1.tgz#631e6db146b06e62fc1c630939acf4a3c77d1b24" + integrity sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ== + +hermes-parser@0.28.1: + version "0.28.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.28.1.tgz#17b9e6377f334b6870a1f6da2e123fdcd0b605ac" + integrity sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg== + dependencies: + hermes-estree "0.28.1" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +meow@^12.1.1: + version "12.1.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" + integrity sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +react-refresh@^0.14.0: + version "0.14.2" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" + integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regexpu-core@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" + integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.12.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve@^1.22.10: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +semver@^5.6.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +type-fest@^4.7.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" + integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" diff --git a/Tests/NodeApi/test_basics.cpp b/Tests/NodeApi/test_basics.cpp new file mode 100644 index 00000000..45d00a26 --- /dev/null +++ b/Tests/NodeApi/test_basics.cpp @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include + +#include +#include +#include "child_process.h" +#include "test_main.h" + +namespace fs = std::filesystem; + +namespace node_api_tests { + +class BasicsTest : public TestFixtureBase { + protected: + void SetUp() override { + const auto& config = Config(); + ASSERT_FALSE(config.js_root.empty()) + << "Node-API test root directory is not configured."; + basics_js_dir_ = config.js_root / "basics"; + } + + ProcessResult RunScript(std::string_view script_filename) noexcept { + const auto& config = Config(); + if (config.run_script) { + return config.run_script(basics_js_dir_ / fs::path{script_filename}); + } + + ProcessResult fallback{}; + fallback.status = 1; + fallback.std_error = "Node-API test runner is not configured."; + return fallback; + } + + bool StringContains(std::string_view str, std::string_view substr) { + return str.find(substr) != std::string::npos; + } + + private: + fs::path basics_js_dir_; +}; + +TEST_F(BasicsTest, TestHello) { + ProcessResult result = RunScript("hello.js"); + ASSERT_TRUE(StringContains(result.std_output, "Hello")); +} + +TEST_F(BasicsTest, TestThrowString) { + ProcessResult result = RunScript("throw_string.js"); + ASSERT_TRUE(StringContains(result.std_error, "Script failed")); +} + +TEST_F(BasicsTest, TestAsyncResolved) { + ProcessResult result = RunScript("async_resolved.js"); + ASSERT_TRUE(StringContains(result.std_output, "test async calling")); + ASSERT_TRUE( + StringContains(result.std_output, "Expected: test async resolved")); +} + +TEST_F(BasicsTest, TestAsyncRejected) { + ProcessResult result = RunScript("async_rejected.js"); + ASSERT_TRUE(StringContains(result.std_output, "test async calling")); + ASSERT_TRUE( + StringContains(result.std_error, "Expected: test async rejected")); +} + +TEST_F(BasicsTest, TestMustCallSuccess) { + ProcessResult result = RunScript("mustcall_success.js"); + ASSERT_TRUE(result.status == 0); +} + +TEST_F(BasicsTest, TestMustCallFailure) { + ProcessResult result = RunScript("mustcall_failure.js"); + ASSERT_TRUE(result.status != 0); + ASSERT_TRUE( + StringContains(result.std_error, "Mismatched noop function calls")); +} + +TEST_F(BasicsTest, TestMustNotCallSuccess) { + ProcessResult result = RunScript("mustnotcall_success.js"); + ASSERT_TRUE(result.status == 0); +} + +TEST_F(BasicsTest, TestMustNotCallFailure) { + ProcessResult result = RunScript("mustnotcall_failure.js"); + ASSERT_TRUE(result.status != 0); + ASSERT_TRUE( + StringContains(result.std_error, "Function should not have been called")); +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/test_main.cpp b/Tests/NodeApi/test_main.cpp new file mode 100644 index 00000000..93803cb8 --- /dev/null +++ b/Tests/NodeApi/test_main.cpp @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "test_main.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "string_utils.h" + +namespace fs = std::filesystem; + +namespace node_api_tests { + +namespace { + +NodeApiTestConfig g_test_config{}; +bool g_test_config_initialized = false; + +const NodeApiTestConfig& RequireConfig() noexcept { + if (!g_test_config_initialized) { + std::cerr << "[NodeApiTests] configuration not initialized." << std::endl; + std::abort(); + } + return g_test_config; +} + +std::string SanitizeName(const std::string& name) { + return ReplaceAll(ReplaceAll(name, "-", "_"), ".", "_"); +} + +} // namespace + +void InitializeNodeApiTests(const NodeApiTestConfig& config) noexcept { + g_test_config = config; + g_test_config_initialized = true; +} + +const NodeApiTestConfig& GetNodeApiTestConfig() noexcept { + return RequireConfig(); +} + +const NodeApiTestConfig& TestFixtureBase::Config() noexcept { + return RequireConfig(); +} + +class NodeApiTestFixture : public TestFixtureBase { + public: + explicit NodeApiTestFixture(fs::path jsFilePath) + : m_jsFilePath(std::move(jsFilePath)) {} + + void TestBody() override { + const auto& config = Config(); + ASSERT_TRUE(static_cast(config.run_script)) + << "Node-API test runner is not configured."; + + ProcessResult result = config.run_script(m_jsFilePath); + if (result.status == 0) { + return; + } + + if (!result.std_error.empty()) { + std::stringstream errorStream(result.std_error); + std::vector errorLines; + std::string line; + while (std::getline(errorStream, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + errorLines.push_back(line); + } + if (errorLines.size() >= 3) { + std::string file = errorLines[0].rfind("file:", 0) == 0 + ? errorLines[0].substr(5) + : ""; + int lineNumber = errorLines[1].rfind("line:", 0) == 0 + ? std::stoi(errorLines[1].substr(5)) + : 0; + std::string message = errorLines[2]; + std::stringstream details; + for (size_t i = 3; i < errorLines.size(); ++i) { + details << errorLines[i] << std::endl; + } + GTEST_MESSAGE_AT_(file.c_str(), + lineNumber, + message.c_str(), + ::testing::TestPartResult::kFatalFailure) + << details.str(); + return; + } + } + + ASSERT_EQ(result.status, 0); + } + + static void Register() { + const auto& config = Config(); + const fs::path& js_root = config.js_root; + if (js_root.empty()) { + std::cerr << "[NodeApiTests] JS root directory not configured." << std::endl; + std::abort(); + } + + for (const fs::directory_entry& dir_entry : + fs::recursive_directory_iterator(js_root)) { + if (!dir_entry.is_regular_file() || + dir_entry.path().extension() != ".js") { + continue; + } + + fs::path jsFilePath = dir_entry.path(); + fs::path suitePath = jsFilePath.parent_path().parent_path(); + std::string suiteFolder = suitePath.filename().string(); + + bool includeTest = false; + if (suiteFolder == "basics") { + includeTest = true; + } else if (suiteFolder == "js-native-api") { + if (config.enabled_native_suites.empty()) { + continue; + } + std::string moduleName = jsFilePath.parent_path().filename().string(); + includeTest = + config.enabled_native_suites.find(moduleName) != + config.enabled_native_suites.end(); + } else { + continue; + } + + if (!includeTest) { + continue; + } + + std::string testSuiteName = SanitizeName(suiteFolder); + std::string testName = SanitizeName( + jsFilePath.parent_path().filename().string() + "_" + + jsFilePath.filename().string()); + + ::testing::RegisterTest( + testSuiteName.c_str(), + testName.c_str(), + nullptr, + nullptr, + jsFilePath.string().c_str(), + 1, + [jsFilePath]() { return new NodeApiTestFixture(jsFilePath); }); + } + } + + private: + fs::path m_jsFilePath; +}; + +void RegisterNodeApiTests() { + NodeApiTestFixture::Register(); +} + +} // namespace node_api_tests diff --git a/Tests/NodeApi/test_main.h b/Tests/NodeApi/test_main.h new file mode 100644 index 00000000..a4fe4532 --- /dev/null +++ b/Tests/NodeApi/test_main.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#ifndef NODE_API_TEST_TEST_MAIN_H +#define NODE_API_TEST_TEST_MAIN_H + +#include +#include +#include +#include + +#include "child_process.h" + +namespace node_api_tests { + +struct NodeApiTestConfig { + std::filesystem::path js_root; + std::function run_script; + std::unordered_set enabled_native_suites; +}; + +void InitializeNodeApiTests(const NodeApiTestConfig& config) noexcept; +const NodeApiTestConfig& GetNodeApiTestConfig() noexcept; +void RegisterNodeApiTests(); + +class TestFixtureBase : public ::testing::Test { + protected: + static const NodeApiTestConfig& Config() noexcept; +}; + +} // namespace node_api_tests + +#endif // !NODE_API_TEST_TEST_MAIN_H diff --git a/Tests/UnitTests/Android/app/build.gradle b/Tests/UnitTests/Android/app/build.gradle index 0151c32d..242dd7d0 100644 --- a/Tests/UnitTests/Android/app/build.gradle +++ b/Tests/UnitTests/Android/app/build.gradle @@ -7,6 +7,9 @@ if (project.hasProperty("jsEngine")) { jsEngine = project.property("jsEngine") } +def nodeApiAssetsDir = "${project.buildDir}/generated/nodeapiassets" +def enableAsan = project.hasProperty("enableAsan") + android { namespace 'com.jsruntimehost.unittests' compileSdk 33 @@ -26,11 +29,15 @@ android { externalNativeBuild { cmake { - arguments ( + def cmakeArgs = [ "-DANDROID_STL=c++_shared", "-DNAPI_JAVASCRIPT_ENGINE=${jsEngine}", "-DJSRUNTIMEHOST_CORE_APPRUNTIME_V8_INSPECTOR=ON" - ) + ] + if (enableAsan) { + cmakeArgs += "-DJSR_ENABLE_ASAN=ON" + } + arguments(*cmakeArgs) } } @@ -61,6 +68,22 @@ android { buildFeatures { viewBinding true } + + packagingOptions { + if (enableAsan) { + doNotStrip "**/*.so" + jniLibs.useLegacyPackaging true + } + } + + sourceSets { + main { + assets.srcDirs += [nodeApiAssetsDir] + if (enableAsan) { + jniLibs.srcDir "${buildDir}/generated/asanRuntime/jniLibs" + } + } + } } dependencies { @@ -91,6 +114,11 @@ task copyScripts { } } +task copyNodeApiTests(type: Copy) { + from '../../../NodeApi/test' + into "${nodeApiAssetsDir}/NodeApi/test" +} + // Run copyScripts task after CMake external build // And make sure merging assets into output is performed after the scripts copy tasks.configureEach { task -> @@ -99,5 +127,77 @@ tasks.configureEach { task -> } if (task.name == 'mergeDebugAssets') { task.dependsOn(copyScripts) + task.dependsOn(copyNodeApiTests) + } + if (task.name == 'mergeReleaseAssets') { + task.dependsOn(copyScripts) + task.dependsOn(copyNodeApiTests) + } +} + +preBuild.dependsOn(copyNodeApiTests) + +if (enableAsan) { + def hostTag = { + def osName = System.getProperty("os.name").toLowerCase() + if (osName.contains("mac") || osName.contains("darwin")) { + return "darwin-x86_64" + } else if (osName.contains("windows")) { + return "windows-x86_64" + } else { + return "linux-x86_64" + } + }.call() + + def asanRuntimeProvider = providers.provider { + def prebuiltRoot = new File(android.ndkDirectory, "toolchains/llvm/prebuilt/${hostTag}/lib64/clang") + if (!prebuiltRoot.exists()) { + throw new GradleException("Unable to locate clang libraries under ${prebuiltRoot}") + } + def versionDir = prebuiltRoot.listFiles().find { it.isDirectory() } + if (versionDir == null) { + throw new GradleException("Unable to determine clang version directory within ${prebuiltRoot}") + } + def runtimeFile = new File(versionDir, "lib/linux/libclang_rt.asan-aarch64-android.so") + if (!runtimeFile.exists()) { + throw new GradleException("Unable to locate ASan runtime library at ${runtimeFile}") + } + return runtimeFile + } + + def generatedAsanDir = layout.buildDirectory.dir("generated/asanRuntime/jniLibs/arm64-v8a") + + def prepareAsanRuntime = tasks.register("prepareAsanRuntime", Copy) { + from(asanRuntimeProvider) + into(generatedAsanDir) + } + + tasks.matching { it.name in ["mergeDebugNativeLibs", "mergeReleaseNativeLibs"] }.configureEach { + dependsOn(prepareAsanRuntime) + } + + def wrapScript = file("${projectDir}/../tools/wrap.sh") + + def pushAsanRuntime = tasks.register("pushAsanRuntime", Exec) { + dependsOn(prepareAsanRuntime) + commandLine("adb", "push", + generatedAsanDir.get().file("libclang_rt.asan-aarch64-android.so").asFile.absolutePath, + "/data/local/tmp/libclang_rt.asan-aarch64-android.so") + } + + def pushAsanWrapScript = tasks.register("pushAsanWrapScript", Exec) { + commandLine("adb", "push", wrapScript.absolutePath, "/data/local/tmp/wrap.sh") + } + + tasks.register("pushAsanArtifacts") { + dependsOn(pushAsanRuntime, pushAsanWrapScript) + doLast { + exec { + commandLine("adb", "shell", "chcon", "u:object_r:zygote_exec:s0", "/data/local/tmp/wrap.sh") + } + exec { + commandLine("adb", "shell", "chmod", "+x", "/data/local/tmp/wrap.sh") + } + } } } diff --git a/Tests/UnitTests/Android/app/src/androidTest/java/com/jsruntimehost/unittests/Main.java b/Tests/UnitTests/Android/app/src/androidTest/java/com/jsruntimehost/unittests/Main.java index 23c22307..069c698d 100644 --- a/Tests/UnitTests/Android/app/src/androidTest/java/com/jsruntimehost/unittests/Main.java +++ b/Tests/UnitTests/Android/app/src/androidTest/java/com/jsruntimehost/unittests/Main.java @@ -23,6 +23,7 @@ public void javaScriptTests() { Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.jsruntimehost.unittests", appContext.getPackageName()); - assertEquals(0, Native.javaScriptTests(appContext)); + Context applicationContext = appContext.getApplicationContext(); + assertEquals(0, Native.javaScriptTests(applicationContext)); } -} \ No newline at end of file +} diff --git a/Tests/UnitTests/Android/app/src/main/cpp/CMakeLists.txt b/Tests/UnitTests/Android/app/src/main/cpp/CMakeLists.txt index 3db2a37a..3f4119db 100644 --- a/Tests/UnitTests/Android/app/src/main/cpp/CMakeLists.txt +++ b/Tests/UnitTests/Android/app/src/main/cpp/CMakeLists.txt @@ -15,15 +15,39 @@ FetchContent_MakeAvailable_With_Message(googletest) npm(install --silent WORKING_DIRECTORY ${TESTS_DIR}) +if(WIN32) + set(NODE_LITE_PLATFORM_SRC ${TESTS_DIR}/NodeApi/node_lite_windows.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC ${TESTS_DIR}/NodeApi/child_process.cpp) +elseif(APPLE) + set(NODE_LITE_PLATFORM_SRC ${TESTS_DIR}/NodeApi/node_lite_posix.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC ${TESTS_DIR}/NodeApi/child_process_posix.cpp) +elseif(ANDROID) + set(NODE_LITE_PLATFORM_SRC ${TESTS_DIR}/NodeApi/node_lite_android.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC ${TESTS_DIR}/NodeApi/child_process_android.cpp) +else() + set(NODE_LITE_PLATFORM_SRC ${TESTS_DIR}/NodeApi/node_lite_posix.cpp) + set(NODE_LITE_CHILD_PROCESS_SRC ${TESTS_DIR}/NodeApi/child_process_posix.cpp) +endif() + add_library(UnitTestsJNI SHARED JNI.cpp ${UNIT_TESTS_DIR}/Shared/Shared.h - ${UNIT_TESTS_DIR}/Shared/Shared.cpp) + ${UNIT_TESTS_DIR}/Shared/Shared.cpp + ${TESTS_DIR}/NodeApi/node_lite.cpp + ${NODE_LITE_PLATFORM_SRC} + ${NODE_LITE_CHILD_PROCESS_SRC} + ${TESTS_DIR}/NodeApi/node_lite_jsruntimehost.cpp + ${TESTS_DIR}/NodeApi/js_runtime_api.cpp + ${TESTS_DIR}/NodeApi/string_utils.cpp + ${TESTS_DIR}/NodeApi/test_main.cpp) target_compile_definitions(UnitTestsJNI PRIVATE JSRUNTIMEHOST_PLATFORM="${JSRUNTIMEHOST_PLATFORM}") target_include_directories(UnitTestsJNI - PRIVATE ${UNIT_TESTS_DIR}) + PRIVATE ${UNIT_TESTS_DIR} + PRIVATE ${TESTS_DIR}/NodeApi + PRIVATE ${TESTS_DIR}/NodeApi/include + PRIVATE ${REPO_ROOT_DIR}/Core/Node-API/Source) target_link_libraries(UnitTestsJNI PRIVATE log @@ -38,4 +62,11 @@ target_link_libraries(UnitTestsJNI PRIVATE XMLHttpRequest PRIVATE WebSocket PRIVATE gtest_main - PRIVATE Blob) + PRIVATE Blob + PRIVATE napi) + +if(ANDROID) + target_link_libraries(UnitTestsJNI PRIVATE dl) +endif() +target_compile_definitions(UnitTestsJNI + PRIVATE NODE_API_AVAILABLE_NATIVE_TESTS="2_function_arguments,3_callbacks,4_object_factory,5_function_factory") diff --git a/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp b/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp index fe243eb5..7f26e796 100644 --- a/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp +++ b/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "Babylon/DebugTrace.h" #include @@ -17,17 +17,30 @@ Java_com_jsruntimehost_unittests_Native_javaScriptTests(JNIEnv* env, jclass claz jclass webSocketClass{env->FindClass("com/jsruntimehost/unittests/WebSocket")}; java::websocket::WebSocketClient::InitializeJavaWebSocketClass(webSocketClass, env); - android::StdoutLogger::Start(); + jclass contextClass = env->GetObjectClass(context); + jmethodID getApplicationContext = env->GetMethodID(contextClass, "getApplicationContext", "()Landroid/content/Context;"); + jobject applicationContext = env->CallObjectMethod(context, getApplicationContext); + env->DeleteLocalRef(contextClass); - android::global::Initialize(javaVM, context); + jclass appContextClass = env->GetObjectClass(applicationContext); + jmethodID getAssets = env->GetMethodID(appContextClass, "getAssets", "()Landroid/content/res/AssetManager;"); + jobject assetManagerObj = env->CallObjectMethod(applicationContext, getAssets); + env->DeleteLocalRef(appContextClass); + + android::global::Initialize(javaVM, applicationContext, assetManagerObj); + + if (assetManagerObj != nullptr) + { + env->DeleteLocalRef(assetManagerObj); + } + + env->DeleteLocalRef(applicationContext); Babylon::DebugTrace::EnableDebugTrace(true); Babylon::DebugTrace::SetTraceOutput([](const char* trace) { printf("%s\n", trace); fflush(stdout); }); auto testResult = RunTests(); - android::StdoutLogger::Stop(); - java::websocket::WebSocketClient::DestructJavaWebSocketClass(env); return testResult; } diff --git a/Tests/UnitTests/Android/gradle.properties b/Tests/UnitTests/Android/gradle.properties index 25ceb3e4..3ec776e3 100644 --- a/Tests/UnitTests/Android/gradle.properties +++ b/Tests/UnitTests/Android/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/Tests/UnitTests/Shared/Shared.cpp b/Tests/UnitTests/Shared/Shared.cpp index 79d4df92..7f835274 100644 --- a/Tests/UnitTests/Shared/Shared.cpp +++ b/Tests/UnitTests/Shared/Shared.cpp @@ -11,9 +11,168 @@ #include #include #include +#include +#include +#include +#include + +#if defined(__ANDROID__) +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../NodeApi/node_lite.h" +#include "../../NodeApi/test_main.h" +#endif namespace { +#if defined(__ANDROID__) + namespace + { + using namespace std::filesystem; + + void CopyAssetsRecursive(AAssetManager* manager, const std::string& asset_path, const path& destination) + { + AAssetDir* dir = AAssetManager_openDir(manager, asset_path.c_str()); + if (dir == nullptr) + { + return; + } + + const char* filename = nullptr; + while ((filename = AAssetDir_getNextFileName(dir)) != nullptr) + { + std::string child_asset = asset_path.empty() ? filename : asset_path + "/" + filename; + AAsset* asset = AAssetManager_open(manager, child_asset.c_str(), AASSET_MODE_STREAMING); + if (asset != nullptr) + { + create_directories(destination); + std::ofstream output(destination / filename, std::ios::binary); + char buffer[4096]; + int read = 0; + while ((read = AAsset_read(asset, buffer, sizeof(buffer))) > 0) + { + output.write(buffer, read); + } + AAsset_close(asset); + } + else + { + CopyAssetsRecursive(manager, child_asset, destination / filename); + } + } + + AAssetDir_close(dir); + } + + path GetFilesDir() + { + JNIEnv* env = android::global::GetEnvForCurrentThread(); + jobject context = android::global::GetAppContext(); + jclass contextClass = env->GetObjectClass(context); + jmethodID getFilesDir = env->GetMethodID(contextClass, "getFilesDir", "()Ljava/io/File;"); + jobject filesDir = env->CallObjectMethod(context, getFilesDir); + env->DeleteLocalRef(contextClass); + + jclass fileClass = env->GetObjectClass(filesDir); + jmethodID getAbsolutePath = env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;"); + jstring pathString = static_cast(env->CallObjectMethod(filesDir, getAbsolutePath)); + env->DeleteLocalRef(fileClass); + + const char* rawPath = env->GetStringUTFChars(pathString, nullptr); + path resultPath{rawPath}; + env->ReleaseStringUTFChars(pathString, rawPath); + env->DeleteLocalRef(pathString); + env->DeleteLocalRef(filesDir); + + return resultPath; + } + + std::unordered_set ParseNativeSuiteList() + { + std::unordered_set suites; +#ifdef NODE_API_AVAILABLE_NATIVE_TESTS + std::stringstream stream(NODE_API_AVAILABLE_NATIVE_TESTS); + std::string entry; + while (std::getline(stream, entry, ',')) + { + if (!entry.empty()) + { + suites.insert(entry); + } + } +#endif + return suites; + } + + std::optional& OverrideBaseDir() + { + static std::optional baseDirOverride{}; + return baseDirOverride; + } + + AAssetManager*& OverrideAssetManager() + { + static AAssetManager* assetManager{}; + return assetManager; + } + + void ConfigureNodeApiTests() + { + static std::once_flag onceFlag; + std::call_once(onceFlag, []() { + path baseDir; + if (OverrideBaseDir()) + { + baseDir = *OverrideBaseDir(); + } + else + { + baseDir = GetFilesDir() / "node_api_tests"; + } + std::error_code ec; + std::filesystem::remove_all(baseDir, ec); + std::filesystem::create_directories(baseDir); + + AAssetManager* assetManagerNative = OverrideAssetManager(); + if (assetManagerNative == nullptr) + { + auto assetManagerWrapper = android::global::GetAppContext().getAssets(); + assetManagerNative = assetManagerWrapper; + } + + if (assetManagerNative != nullptr) + { + CopyAssetsRecursive(assetManagerNative, "NodeApi/test", baseDir); + } + + node_api_tests::NodeApiTestConfig config{}; + config.js_root = baseDir; + config.run_script = [baseDir](const path& script) { + node_api_tests::NodeLiteRuntime::Callbacks callbacks; + callbacks.stdout_callback = [](const std::string& message) { + __android_log_write(ANDROID_LOG_INFO, "NodeApiTests", message.c_str()); + }; + callbacks.stderr_callback = [](const std::string& message) { + __android_log_write(ANDROID_LOG_ERROR, "NodeApiTests", message.c_str()); + }; + return node_api_tests::RunNodeLiteScript(baseDir, script, std::move(callbacks)); + }; + config.enabled_native_suites = ParseNativeSuiteList(); + + node_api_tests::InitializeNodeApiTests(config); + }); + } + } +#endif + const char* EnumToString(Babylon::Polyfills::Console::LogLevel logLevel) { switch (logLevel) @@ -117,6 +276,19 @@ TEST(Console, Log) int RunTests() { +#if defined(__ANDROID__) + ConfigureNodeApiTests(); +#endif testing::InitGoogleTest(); +#if defined(__ANDROID__) + node_api_tests::RegisterNodeApiTests(); +#endif return RUN_ALL_TESTS(); } +#if defined(__ANDROID__) +void SetNodeApiTestEnvironment(AAssetManager* assetManager, const std::filesystem::path& baseDir) +{ + OverrideAssetManager() = assetManager; + OverrideBaseDir() = baseDir; +} +#endif diff --git a/Tests/UnitTests/Shared/Shared.h b/Tests/UnitTests/Shared/Shared.h index b1610fa8..acc4548b 100644 --- a/Tests/UnitTests/Shared/Shared.h +++ b/Tests/UnitTests/Shared/Shared.h @@ -1,3 +1,5 @@ #pragma once -int RunTests(); \ No newline at end of file +#include + +int RunTests(); diff --git a/Tests/package-lock.json b/Tests/package-lock.json index e627a786..3964da22 100644 --- a/Tests/package-lock.json +++ b/Tests/package-lock.json @@ -111,6 +111,7 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2384,6 +2385,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2410,6 +2412,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2676,6 +2679,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -5152,6 +5156,7 @@ "integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -5201,6 +5206,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1",