| // Copyright 2026 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. 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. |
| // |
| // 3. Neither the name of the copyright holder 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 HOLDER 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. |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" |
| |
| #include <gtest/gtest.h> |
| |
| #include <array> |
| #include <cfloat> |
| #include <climits> |
| #include <cmath> |
| #include <limits> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "src/dawn/node/napi_v8/napi_v8.h" |
| #include "src/dawn/node/test/V8TestEnvironment.h" |
| |
| #ifndef V8_ENABLE_SANDBOX |
| #define V8_ENABLE_SANDBOX 0 |
| #endif |
| |
| namespace { |
| |
| // Test fixture providing an isolated V8 environment and napi_env for each test case. |
| class NapiV8Test : public dawn::node::test::V8IsolateTest { |
| protected: |
| NapiV8Test() : V8IsolateTest(v8::MicrotasksPolicy::kAuto) {} |
| |
| void SetUp() override { |
| V8IsolateTest::SetUp(); |
| env_ = dawn::napi_v8::CreateEnv(isolate_, context()); |
| napi_value setup_script = nullptr; |
| ASSERT_EQ(napi_create_string_utf8( |
| env_, |
| "globalThis._immediateQueue = [];" |
| "globalThis.setImmediate = (fn) => { globalThis._immediateQueue.push(fn); };", |
| NAPI_AUTO_LENGTH, &setup_script), |
| napi_ok); |
| napi_value unused = nullptr; |
| ASSERT_EQ(napi_run_script(env_, setup_script, &unused), napi_ok); |
| } |
| |
| void TearDown() override { |
| dawn::napi_v8::DestroyEnv(env_); |
| V8IsolateTest::TearDown(); |
| } |
| |
| napi_env env_ = nullptr; |
| |
| napi_env CreateSubEnv() { return dawn::napi_v8::CreateEnv(isolate_, env_->GetContext()); } |
| |
| void DestroySubEnv(napi_env sub_env) { dawn::napi_v8::DestroyEnv(sub_env); } |
| |
| void DrainImmediates() { |
| napi_value drain_script = nullptr; |
| ASSERT_EQ(napi_create_string_utf8(env_, |
| "while (globalThis._immediateQueue.length > 0) {" |
| " globalThis._immediateQueue.shift()();" |
| "}", |
| NAPI_AUTO_LENGTH, &drain_script), |
| napi_ok); |
| napi_value unused = nullptr; |
| ASSERT_EQ(napi_run_script(env_, drain_script, &unused), napi_ok); |
| } |
| |
| void RequestGC() { |
| isolate_->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection); |
| DrainImmediates(); |
| } |
| |
| // Executes pending JavaScript microtasks (such as Promise .then() / .catch() callbacks). |
| void RunMicrotasks() { isolate_->PerformMicrotaskCheckpoint(); } |
| }; |
| |
| // ============================================================================ |
| // Scopes Tests |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, HandleScopeLifecycle) { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value val; |
| ASSERT_EQ(napi_create_int32(env_, 42, &val), napi_ok); |
| int32_t num = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, val, &num), napi_ok); |
| EXPECT_EQ(num, 42); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Tests opening multiple nested handle scopes and closing them in LIFO order. |
| TEST_F(NapiV8Test, MultipleNestedHandleScopes) { |
| napi_handle_scope scope1; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope1), napi_ok); |
| napi_value val1; |
| ASSERT_EQ(napi_create_int32(env_, 100, &val1), napi_ok); |
| |
| napi_handle_scope scope2; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope2), napi_ok); |
| napi_value val2; |
| ASSERT_EQ(napi_create_int32(env_, 200, &val2), napi_ok); |
| |
| napi_handle_scope scope3; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope3), napi_ok); |
| napi_value val3; |
| ASSERT_EQ(napi_create_int32(env_, 300, &val3), napi_ok); |
| |
| // All values readable while all 3 scopes are open |
| int32_t num = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, val1, &num), napi_ok); |
| EXPECT_EQ(num, 100); |
| ASSERT_EQ(napi_get_value_int32(env_, val2, &num), napi_ok); |
| EXPECT_EQ(num, 200); |
| ASSERT_EQ(napi_get_value_int32(env_, val3, &num), napi_ok); |
| EXPECT_EQ(num, 300); |
| |
| // Close scope 3; outer scopes remain valid |
| ASSERT_EQ(napi_close_handle_scope(env_, scope3), napi_ok); |
| ASSERT_EQ(napi_get_value_int32(env_, val1, &num), napi_ok); |
| EXPECT_EQ(num, 100); |
| ASSERT_EQ(napi_get_value_int32(env_, val2, &num), napi_ok); |
| EXPECT_EQ(num, 200); |
| |
| // Close scope 2; outermost scope remains valid |
| ASSERT_EQ(napi_close_handle_scope(env_, scope2), napi_ok); |
| ASSERT_EQ(napi_get_value_int32(env_, val1, &num), napi_ok); |
| EXPECT_EQ(num, 100); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope1), napi_ok); |
| } |
| |
| // Tests that attempting to close handle scopes out-of-order returns napi_handle_scope_mismatch. |
| TEST_F(NapiV8Test, HandleScopeMismatch) { |
| napi_handle_scope outer_scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &outer_scope), napi_ok); |
| |
| napi_handle_scope inner_scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &inner_scope), napi_ok); |
| |
| // Attempting to close outer scope before inner scope should fail with mismatch |
| EXPECT_EQ(napi_close_handle_scope(env_, outer_scope), napi_handle_scope_mismatch); |
| |
| const napi_extended_error_info* error_info = nullptr; |
| ASSERT_EQ(napi_get_last_error_info(env_, &error_info), napi_ok); |
| EXPECT_EQ(error_info->error_code, napi_handle_scope_mismatch); |
| ASSERT_NE(error_info->error_message, nullptr); |
| EXPECT_STREQ(error_info->error_message, "Handle scope closed out of order"); |
| |
| // Proper LIFO closing succeeds |
| ASSERT_EQ(napi_close_handle_scope(env_, inner_scope), napi_ok); |
| ASSERT_EQ(napi_close_handle_scope(env_, outer_scope), napi_ok); |
| } |
| |
| // ============================================================================ |
| // Primitives Tests |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, SingletonsAndBooleans) { |
| napi_value undefined_val; |
| ASSERT_EQ(napi_get_undefined(env_, &undefined_val), napi_ok); |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, undefined_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_undefined); |
| |
| napi_value null_val; |
| ASSERT_EQ(napi_get_null(env_, &null_val), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, null_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_null); |
| |
| napi_value global_val; |
| ASSERT_EQ(napi_get_global(env_, &global_val), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, global_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_object); |
| |
| napi_value true_val; |
| ASSERT_EQ(napi_get_boolean(env_, true, &true_val), napi_ok); |
| bool bool_res = false; |
| ASSERT_EQ(napi_get_value_bool(env_, true_val, &bool_res), napi_ok); |
| EXPECT_TRUE(bool_res); |
| |
| napi_value false_val; |
| ASSERT_EQ(napi_get_boolean(env_, false, &false_val), napi_ok); |
| ASSERT_EQ(napi_get_value_bool(env_, false_val, &bool_res), napi_ok); |
| EXPECT_FALSE(bool_res); |
| } |
| |
| TEST_F(NapiV8Test, Numbers) { |
| napi_value int_val; |
| ASSERT_EQ(napi_create_int32(env_, -12345, &int_val), napi_ok); |
| int32_t i32 = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, int_val, &i32), napi_ok); |
| EXPECT_EQ(i32, -12345); |
| |
| napi_value uint_val; |
| ASSERT_EQ(napi_create_uint32(env_, 987654u, &uint_val), napi_ok); |
| uint32_t u32 = 0; |
| ASSERT_EQ(napi_get_value_uint32(env_, uint_val, &u32), napi_ok); |
| EXPECT_EQ(u32, 987654u); |
| |
| napi_value double_val; |
| ASSERT_EQ(napi_create_double(env_, 3.1415926535, &double_val), napi_ok); |
| double d = 0.0; |
| ASSERT_EQ(napi_get_value_double(env_, double_val, &d), napi_ok); |
| EXPECT_DOUBLE_EQ(d, 3.1415926535); |
| |
| napi_value int64_val; |
| ASSERT_EQ(napi_create_int64(env_, 9007199254740991LL, &int64_val), napi_ok); |
| int64_t i64 = 0; |
| ASSERT_EQ(napi_get_value_int64(env_, int64_val, &i64), napi_ok); |
| EXPECT_EQ(i64, 9007199254740991LL); |
| } |
| |
| TEST_F(NapiV8Test, NumberBoundaryValues) { |
| // 32-bit integer limits |
| napi_value min_i32, max_i32; |
| ASSERT_EQ(napi_create_int32(env_, INT32_MIN, &min_i32), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, INT32_MAX, &max_i32), napi_ok); |
| int32_t i32_res = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, min_i32, &i32_res), napi_ok); |
| EXPECT_EQ(i32_res, INT32_MIN); |
| ASSERT_EQ(napi_get_value_int32(env_, max_i32, &i32_res), napi_ok); |
| EXPECT_EQ(i32_res, INT32_MAX); |
| |
| // Unsigned 32-bit limit |
| napi_value max_u32; |
| ASSERT_EQ(napi_create_uint32(env_, UINT32_MAX, &max_u32), napi_ok); |
| uint32_t u32_res = 0; |
| ASSERT_EQ(napi_get_value_uint32(env_, max_u32, &u32_res), napi_ok); |
| EXPECT_EQ(u32_res, UINT32_MAX); |
| |
| // Floating-point special values: Infinity, -Infinity, NaN |
| napi_value inf_val, neg_inf_val, nan_val; |
| ASSERT_EQ(napi_create_double(env_, std::numeric_limits<double>::infinity(), &inf_val), napi_ok); |
| ASSERT_EQ(napi_create_double(env_, -std::numeric_limits<double>::infinity(), &neg_inf_val), |
| napi_ok); |
| ASSERT_EQ(napi_create_double(env_, std::numeric_limits<double>::quiet_NaN(), &nan_val), |
| napi_ok); |
| |
| double d_res = 0.0; |
| ASSERT_EQ(napi_get_value_double(env_, inf_val, &d_res), napi_ok); |
| EXPECT_TRUE(std::isinf(d_res) && d_res > 0); |
| ASSERT_EQ(napi_get_value_double(env_, neg_inf_val, &d_res), napi_ok); |
| EXPECT_TRUE(std::isinf(d_res) && d_res < 0); |
| ASSERT_EQ(napi_get_value_double(env_, nan_val, &d_res), napi_ok); |
| EXPECT_TRUE(std::isnan(d_res)); |
| } |
| |
| TEST_F(NapiV8Test, Strings) { |
| const char* test_str = "WebGPU CTS Runner on V8"; |
| napi_value str_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, test_str, NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| |
| size_t length = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_val, nullptr, 0, &length), napi_ok); |
| EXPECT_EQ(length, strlen(test_str)); |
| |
| std::vector<char> buf(length + 1); |
| size_t written = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_val, buf.data(), buf.size(), &written), napi_ok); |
| EXPECT_EQ(written, length); |
| EXPECT_STREQ(buf.data(), test_str); |
| } |
| |
| // Tests string buffer sizing, truncation, and null-termination in napi_get_value_string_utf8. |
| TEST_F(NapiV8Test, StringUtf8TruncationAndNullTermination) { |
| const char* test_str = "Hello, World!"; |
| napi_value str_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, test_str, NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| |
| // Buffer smaller than string: capacity 6 should copy 5 chars + '\0' |
| char small_buf[6] = {0}; |
| size_t written = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_val, small_buf, sizeof(small_buf), &written), |
| napi_ok); |
| EXPECT_EQ(written, 5u); |
| EXPECT_STREQ(small_buf, "Hello"); |
| } |
| |
| // Tests that when the destination buffer is larger than the string, the null-terminator |
| // is placed immediately after the copied string and remaining buffer memory is untouched. |
| TEST_F(NapiV8Test, StringUtf8OversizedBuffer) { |
| const char* test_str = "hello"; |
| napi_value str_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, test_str, NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| |
| std::array<char, 32> large_buf; |
| large_buf.fill('x'); |
| size_t written = 0; |
| ASSERT_EQ( |
| napi_get_value_string_utf8(env_, str_val, large_buf.data(), large_buf.size(), &written), |
| napi_ok); |
| EXPECT_EQ(written, 5u); |
| EXPECT_STREQ(large_buf.data(), "hello"); |
| EXPECT_EQ(large_buf[5], '\0'); |
| EXPECT_EQ(large_buf[6], 'x'); |
| } |
| |
| // Tests calling napi_get_value_string_utf8 with non-null buf and bufsize == 0. |
| // Verifies that nothing is written to buf (not even '\0') and result is set to 0. |
| TEST_F(NapiV8Test, StringUtf8ZeroBufferSize) { |
| const char* test_str = "hello"; |
| napi_value str_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, test_str, NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| |
| char sentinel_buf[4] = {'a', 'b', 'c', 'd'}; |
| size_t written = 999; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_val, sentinel_buf, 0, &written), napi_ok); |
| EXPECT_EQ(written, 0u); |
| EXPECT_EQ(sentinel_buf[0], 'a'); |
| EXPECT_EQ(sentinel_buf[1], 'b'); |
| EXPECT_EQ(sentinel_buf[2], 'c'); |
| EXPECT_EQ(sentinel_buf[3], 'd'); |
| |
| // Also test with result == nullptr |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_val, sentinel_buf, 0, nullptr), napi_ok); |
| EXPECT_EQ(sentinel_buf[0], 'a'); |
| } |
| |
| // Tests empty string handling. |
| TEST_F(NapiV8Test, EmptyStrings) { |
| napi_value empty_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "", 0, &empty_val), napi_ok); |
| |
| size_t length = 99; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, empty_val, nullptr, 0, &length), napi_ok); |
| EXPECT_EQ(length, 0u); |
| |
| char buf[4] = {'a', 'b', 'c', 'd'}; |
| size_t written = 99; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, empty_val, buf, sizeof(buf), &written), napi_ok); |
| EXPECT_EQ(written, 0u); |
| EXPECT_EQ(buf[0], '\0'); |
| } |
| |
| TEST_F(NapiV8Test, Symbols) { |
| napi_value desc; |
| ASSERT_EQ(napi_create_string_utf8(env_, "sym_desc", NAPI_AUTO_LENGTH, &desc), napi_ok); |
| napi_value sym; |
| ASSERT_EQ(napi_create_symbol(env_, desc, &sym), napi_ok); |
| |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, sym, &type), napi_ok); |
| EXPECT_EQ(type, napi_symbol); |
| } |
| |
| TEST_F(NapiV8Test, CoercionsAndStrictEquals) { |
| napi_value num_val; |
| ASSERT_EQ(napi_create_int32(env_, 100, &num_val), napi_ok); |
| |
| napi_value str_coerced; |
| ASSERT_EQ(napi_coerce_to_string(env_, num_val, &str_coerced), napi_ok); |
| char buf[16] = {0}; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, str_coerced, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "100"); |
| |
| napi_value num2_val; |
| ASSERT_EQ(napi_create_int32(env_, 100, &num2_val), napi_ok); |
| bool equals = false; |
| ASSERT_EQ(napi_strict_equals(env_, num_val, num2_val, &equals), napi_ok); |
| EXPECT_TRUE(equals); |
| |
| napi_value diff_val; |
| ASSERT_EQ(napi_create_int32(env_, 200, &diff_val), napi_ok); |
| ASSERT_EQ(napi_strict_equals(env_, num_val, diff_val, &equals), napi_ok); |
| EXPECT_FALSE(equals); |
| } |
| |
| // Tests comprehensive napi_get_last_error_info lifecycle: initial state, argument validation, |
| // error code recording, reading idempotence, and clearing on subsequent successful calls. |
| TEST_F(NapiV8Test, GetLastErrorInfo) { |
| const napi_extended_error_info* info = nullptr; |
| |
| // 1. Initial state: clean environment should report napi_ok |
| ASSERT_EQ(napi_get_last_error_info(env_, &info), napi_ok); |
| ASSERT_NE(info, nullptr); |
| EXPECT_EQ(info->error_code, napi_ok); |
| EXPECT_EQ(info->error_message, nullptr); |
| EXPECT_EQ(info->engine_error_code, 0u); |
| EXPECT_EQ(info->engine_reserved, nullptr); |
| |
| // 2. Argument validation on napi_get_last_error_info itself |
| EXPECT_EQ(napi_get_last_error_info(nullptr, &info), napi_invalid_arg); |
| EXPECT_EQ(napi_get_last_error_info(env_, nullptr), napi_invalid_arg); |
| |
| // 3. Triggering an error records napi_invalid_arg and error message |
| EXPECT_EQ(napi_create_int32(env_, 10, nullptr), napi_invalid_arg); |
| ASSERT_EQ(napi_get_last_error_info(env_, &info), napi_ok); |
| ASSERT_NE(info, nullptr); |
| EXPECT_EQ(info->error_code, napi_invalid_arg); |
| ASSERT_NE(info->error_message, nullptr); |
| EXPECT_STREQ(info->error_message, "Invalid argument: null pointer"); |
| |
| // 4. Reading error info multiple times is idempotent (does not clear or alter error) |
| const napi_extended_error_info* info2 = nullptr; |
| ASSERT_EQ(napi_get_last_error_info(env_, &info2), napi_ok); |
| EXPECT_EQ(info2->error_code, napi_invalid_arg); |
| ASSERT_NE(info2->error_message, nullptr); |
| EXPECT_STREQ(info2->error_message, "Invalid argument: null pointer"); |
| |
| // 5. Triggering a different error code overwrites last_error_info and error message |
| napi_value bool_val; |
| ASSERT_EQ(napi_get_boolean(env_, true, &bool_val), napi_ok); |
| char str_buf[16]; |
| EXPECT_EQ(napi_get_value_string_utf8(env_, bool_val, str_buf, sizeof(str_buf), nullptr), |
| napi_string_expected); |
| ASSERT_EQ(napi_get_last_error_info(env_, &info), napi_ok); |
| EXPECT_EQ(info->error_code, napi_string_expected); |
| ASSERT_NE(info->error_message, nullptr); |
| EXPECT_STREQ(info->error_message, "A string was expected"); |
| |
| // 6. Subsequent successful API call resets last_error_info back to napi_ok and clears message |
| napi_value num_val; |
| ASSERT_EQ(napi_create_int32(env_, 42, &num_val), napi_ok); |
| ASSERT_EQ(napi_get_last_error_info(env_, &info), napi_ok); |
| EXPECT_EQ(info->error_code, napi_ok); |
| EXPECT_EQ(info->error_message, nullptr); |
| } |
| |
| // Tests invalid argument validation and error propagation. |
| TEST_F(NapiV8Test, InvalidArgumentsAndErrorHandling) { |
| // Passing null environment |
| EXPECT_EQ(napi_create_int32(nullptr, 10, nullptr), napi_invalid_arg); |
| |
| // Passing null output pointer |
| EXPECT_EQ(napi_create_int32(env_, 10, nullptr), napi_invalid_arg); |
| |
| // Type mismatch for extraction |
| napi_value bool_val; |
| ASSERT_EQ(napi_get_boolean(env_, true, &bool_val), napi_ok); |
| char str_buf[16]; |
| EXPECT_EQ(napi_get_value_string_utf8(env_, bool_val, str_buf, sizeof(str_buf), nullptr), |
| napi_string_expected); |
| } |
| |
| // ============================================================================ |
| // Object & Property Tests |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, ObjectsAndNamedProperties) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, obj, &type), napi_ok); |
| EXPECT_EQ(type, napi_object); |
| |
| // Initially property does not exist |
| bool has_prop = true; |
| ASSERT_EQ(napi_has_named_property(env_, obj, "foo", &has_prop), napi_ok); |
| EXPECT_FALSE(has_prop); |
| |
| napi_value get_val; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "foo", &get_val), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, get_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_undefined); |
| |
| // Set named property |
| napi_value str_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "bar", NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "foo", str_val), napi_ok); |
| |
| // Check has property is true |
| ASSERT_EQ(napi_has_named_property(env_, obj, "foo", &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| // Get property value |
| ASSERT_EQ(napi_get_named_property(env_, obj, "foo", &get_val), napi_ok); |
| char buf[16]; |
| size_t written = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, get_val, buf, sizeof(buf), &written), napi_ok); |
| EXPECT_STREQ(buf, "bar"); |
| EXPECT_EQ(written, 3u); |
| } |
| |
| TEST_F(NapiV8Test, ObjectsAndValueProperties) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| // 1. Initially, non-existent properties return false for has_property and undefined for |
| // get_property |
| napi_value non_existent_name; |
| ASSERT_EQ(napi_create_string_utf8(env_, "missing", NAPI_AUTO_LENGTH, &non_existent_name), |
| napi_ok); |
| bool has_prop = true; |
| ASSERT_EQ(napi_has_property(env_, obj, non_existent_name, &has_prop), napi_ok); |
| EXPECT_FALSE(has_prop); |
| |
| napi_value missing_val; |
| ASSERT_EQ(napi_get_property(env_, obj, non_existent_name, &missing_val), napi_ok); |
| napi_valuetype val_type; |
| ASSERT_EQ(napi_typeof(env_, missing_val, &val_type), napi_ok); |
| EXPECT_EQ(val_type, napi_undefined); |
| |
| // 2. Non-existent symbol returns false |
| napi_value missing_sym; |
| ASSERT_EQ(napi_create_symbol(env_, nullptr, &missing_sym), napi_ok); |
| ASSERT_EQ(napi_has_property(env_, obj, missing_sym, &has_prop), napi_ok); |
| EXPECT_FALSE(has_prop); |
| |
| // 3. String key |
| napi_value name_str, val_num; |
| ASSERT_EQ(napi_create_string_utf8(env_, "key1", NAPI_AUTO_LENGTH, &name_str), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 1234, &val_num), napi_ok); |
| ASSERT_EQ(napi_set_property(env_, obj, name_str, val_num), napi_ok); |
| |
| has_prop = false; |
| ASSERT_EQ(napi_has_property(env_, obj, name_str, &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| napi_value get_val; |
| ASSERT_EQ(napi_get_property(env_, obj, name_str, &get_val), napi_ok); |
| int32_t num = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, get_val, &num), napi_ok); |
| EXPECT_EQ(num, 1234); |
| |
| // 4. Symbol key |
| napi_value sym_name, sym_val; |
| ASSERT_EQ(napi_create_symbol(env_, nullptr, &sym_name), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "sym_value", NAPI_AUTO_LENGTH, &sym_val), napi_ok); |
| ASSERT_EQ(napi_set_property(env_, obj, sym_name, sym_val), napi_ok); |
| |
| ASSERT_EQ(napi_has_property(env_, obj, sym_name, &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| ASSERT_EQ(napi_get_property(env_, obj, sym_name, &get_val), napi_ok); |
| char buf[16]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, get_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "sym_value"); |
| } |
| |
| TEST_F(NapiV8Test, ObjectPropertyNames) { |
| // 1. Empty object returns array of length 0 |
| napi_value empty_obj; |
| ASSERT_EQ(napi_create_object(env_, &empty_obj), napi_ok); |
| napi_value empty_names; |
| ASSERT_EQ(napi_get_property_names(env_, empty_obj, &empty_names), napi_ok); |
| uint32_t empty_len = 999; |
| ASSERT_EQ(napi_get_array_length(env_, empty_names, &empty_len), napi_ok); |
| EXPECT_EQ(empty_len, 0u); |
| |
| // 2. Object with properties |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| napi_value val1, val2, val3; |
| ASSERT_EQ(napi_create_int32(env_, 1, &val1), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 2, &val2), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 3, &val3), napi_ok); |
| |
| ASSERT_EQ(napi_set_named_property(env_, obj, "alpha", val1), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "beta", val2), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "gamma", val3), napi_ok); |
| |
| napi_value names; |
| ASSERT_EQ(napi_get_property_names(env_, obj, &names), napi_ok); |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, names, &type), napi_ok); |
| EXPECT_EQ(type, napi_object); |
| |
| uint32_t len = 0; |
| ASSERT_EQ(napi_get_array_length(env_, names, &len), napi_ok); |
| EXPECT_EQ(len, 3u); |
| |
| napi_value elem0, elem1, elem2; |
| ASSERT_EQ(napi_get_element(env_, names, 0, &elem0), napi_ok); |
| ASSERT_EQ(napi_get_element(env_, names, 1, &elem1), napi_ok); |
| ASSERT_EQ(napi_get_element(env_, names, 2, &elem2), napi_ok); |
| |
| char buf[16]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, elem0, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "alpha"); |
| ASSERT_EQ(napi_get_value_string_utf8(env_, elem1, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "beta"); |
| ASSERT_EQ(napi_get_value_string_utf8(env_, elem2, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "gamma"); |
| } |
| |
| TEST_F(NapiV8Test, ObjectPrototypeProperties) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| // "toString" is on Object.prototype: has_property and has_named_property return true |
| bool has_prop = false; |
| ASSERT_EQ(napi_has_named_property(env_, obj, "toString", &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| napi_value to_string_name; |
| ASSERT_EQ(napi_create_string_utf8(env_, "toString", NAPI_AUTO_LENGTH, &to_string_name), |
| napi_ok); |
| ASSERT_EQ(napi_has_property(env_, obj, to_string_name, &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| napi_value fn_val; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "toString", &fn_val), napi_ok); |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, fn_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_function); |
| } |
| |
| // ============================================================================ |
| // Array Tests |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, ArraysAndElements) { |
| // 1. Create empty array |
| napi_value arr; |
| ASSERT_EQ(napi_create_array(env_, &arr), napi_ok); |
| napi_valuetype type; |
| ASSERT_EQ(napi_typeof(env_, arr, &type), napi_ok); |
| EXPECT_EQ(type, napi_object); |
| |
| uint32_t len = 999; |
| ASSERT_EQ(napi_get_array_length(env_, arr, &len), napi_ok); |
| EXPECT_EQ(len, 0u); |
| |
| // 2. Set elements |
| napi_value str1, str2; |
| ASSERT_EQ(napi_create_string_utf8(env_, "first", NAPI_AUTO_LENGTH, &str1), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "second", NAPI_AUTO_LENGTH, &str2), napi_ok); |
| |
| ASSERT_EQ(napi_set_element(env_, arr, 0, str1), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, arr, 1, str2), napi_ok); |
| |
| ASSERT_EQ(napi_get_array_length(env_, arr, &len), napi_ok); |
| EXPECT_EQ(len, 2u); |
| |
| // 3. Get element |
| napi_value elem_val; |
| ASSERT_EQ(napi_get_element(env_, arr, 1, &elem_val), napi_ok); |
| char buf[16]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, elem_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "second"); |
| |
| // 4. Out-of-bounds get element returns undefined |
| napi_value oob_val; |
| ASSERT_EQ(napi_get_element(env_, arr, 99, &oob_val), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, oob_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_undefined); |
| |
| // 5. Sparse array: setting element at index 5 grows length to 6 |
| napi_value str_sparse; |
| ASSERT_EQ(napi_create_string_utf8(env_, "sparse", NAPI_AUTO_LENGTH, &str_sparse), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, arr, 5, str_sparse), napi_ok); |
| ASSERT_EQ(napi_get_array_length(env_, arr, &len), napi_ok); |
| EXPECT_EQ(len, 6u); |
| |
| // Hole index 3 returns undefined |
| napi_value hole_val; |
| ASSERT_EQ(napi_get_element(env_, arr, 3, &hole_val), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, hole_val, &type), napi_ok); |
| EXPECT_EQ(type, napi_undefined); |
| |
| // 6. Create array with length |
| napi_value arr2; |
| ASSERT_EQ(napi_create_array_with_length(env_, 10, &arr2), napi_ok); |
| ASSERT_EQ(napi_get_array_length(env_, arr2, &len), napi_ok); |
| EXPECT_EQ(len, 10u); |
| } |
| |
| TEST_F(NapiV8Test, IndexedPropertiesOnPlainObject) { |
| // JavaScript objects can have indexed elements |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| napi_value val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "indexed_val", NAPI_AUTO_LENGTH, &val), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, obj, 0, val), napi_ok); |
| |
| napi_value get_val; |
| ASSERT_EQ(napi_get_element(env_, obj, 0, &get_val), napi_ok); |
| char buf[32]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, get_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "indexed_val"); |
| |
| bool has_named = false; |
| ASSERT_EQ(napi_has_named_property(env_, obj, "0", &has_named), napi_ok); |
| EXPECT_TRUE(has_named); |
| } |
| |
| TEST_F(NapiV8Test, ObjectAndArrayInvalidArgs) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_value arr; |
| ASSERT_EQ(napi_create_array(env_, &arr), napi_ok); |
| napi_value name; |
| ASSERT_EQ(napi_create_string_utf8(env_, "k", NAPI_AUTO_LENGTH, &name), napi_ok); |
| napi_value val; |
| ASSERT_EQ(napi_create_int32(env_, 1, &val), napi_ok); |
| |
| // 1. napi_create_object / napi_create_array |
| EXPECT_EQ(napi_create_object(nullptr, &obj), napi_invalid_arg); |
| EXPECT_EQ(napi_create_object(env_, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_array(nullptr, &arr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_array(env_, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_array_with_length(nullptr, 5, &arr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_array_with_length(env_, 5, nullptr), napi_invalid_arg); |
| |
| // 2. napi_get_property_names |
| napi_value names; |
| EXPECT_EQ(napi_get_property_names(nullptr, obj, &names), napi_invalid_arg); |
| EXPECT_EQ(napi_get_property_names(env_, nullptr, &names), napi_invalid_arg); |
| EXPECT_EQ(napi_get_property_names(env_, obj, nullptr), napi_invalid_arg); |
| |
| // 3. napi_set_property / napi_get_property / napi_has_property |
| napi_value get_res; |
| bool has_res; |
| EXPECT_EQ(napi_set_property(nullptr, obj, name, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_property(env_, nullptr, name, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_property(env_, obj, nullptr, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_property(env_, obj, name, nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_get_property(nullptr, obj, name, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_property(env_, nullptr, name, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_property(env_, obj, nullptr, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_property(env_, obj, name, nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_has_property(nullptr, obj, name, &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_property(env_, nullptr, name, &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_property(env_, obj, nullptr, &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_property(env_, obj, name, nullptr), napi_invalid_arg); |
| |
| // 4. napi_set_named_property / napi_get_named_property / napi_has_named_property |
| EXPECT_EQ(napi_set_named_property(nullptr, obj, "k", val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_named_property(env_, nullptr, "k", val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_named_property(env_, obj, nullptr, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_named_property(env_, obj, "k", nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_get_named_property(nullptr, obj, "k", &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_named_property(env_, nullptr, "k", &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_named_property(env_, obj, nullptr, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_named_property(env_, obj, "k", nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_has_named_property(nullptr, obj, "k", &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_named_property(env_, nullptr, "k", &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_named_property(env_, obj, nullptr, &has_res), napi_invalid_arg); |
| EXPECT_EQ(napi_has_named_property(env_, obj, "k", nullptr), napi_invalid_arg); |
| |
| // 5. napi_get_element / napi_set_element |
| EXPECT_EQ(napi_get_element(nullptr, arr, 0, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_element(env_, nullptr, 0, &get_res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_element(env_, arr, 0, nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_set_element(nullptr, arr, 0, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_element(env_, nullptr, 0, val), napi_invalid_arg); |
| EXPECT_EQ(napi_set_element(env_, arr, 0, nullptr), napi_invalid_arg); |
| |
| // 6. Calling array_length on non-array |
| uint32_t len = 0; |
| EXPECT_EQ(napi_get_array_length(nullptr, arr, &len), napi_invalid_arg); |
| EXPECT_EQ(napi_get_array_length(env_, nullptr, &len), napi_invalid_arg); |
| EXPECT_EQ(napi_get_array_length(env_, arr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_get_array_length(env_, obj, &len), napi_array_expected); |
| |
| const napi_extended_error_info* info = nullptr; |
| ASSERT_EQ(napi_get_last_error_info(env_, &info), napi_ok); |
| EXPECT_EQ(info->error_code, napi_array_expected); |
| ASSERT_NE(info->error_message, nullptr); |
| EXPECT_STREQ(info->error_message, "An array was expected"); |
| } |
| |
| // ============================================================================ |
| // Stage 4: Functions, Classes & Properties Tests |
| // ============================================================================ |
| |
| // 1. Function Creation & Metadata |
| TEST_F(NapiV8Test, FunctionCreationAndTypeOf) { |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "testFn", NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), |
| napi_ok); |
| napi_valuetype fn_type; |
| ASSERT_EQ(napi_typeof(env_, fn, &fn_type), napi_ok); |
| EXPECT_EQ(fn_type, napi_function); |
| } |
| |
| TEST_F(NapiV8Test, FunctionNameAutoLength) { |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| napi_value fn; |
| ASSERT_EQ( |
| napi_create_function(env_, "myAwesomeFunction", NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), |
| napi_ok); |
| napi_value name_val; |
| ASSERT_EQ(napi_get_named_property(env_, fn, "name", &name_val), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, name_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "myAwesomeFunction"); |
| } |
| |
| TEST_F(NapiV8Test, FunctionNameExplicitLength) { |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "longFunctionName", 4, noop_cb, nullptr, &fn), napi_ok); |
| napi_value name_val; |
| ASSERT_EQ(napi_get_named_property(env_, fn, "name", &name_val), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, name_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "long"); |
| } |
| |
| TEST_F(NapiV8Test, FunctionAnonymous) { |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, nullptr, NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), |
| napi_ok); |
| napi_value name_val; |
| ASSERT_EQ(napi_get_named_property(env_, fn, "name", &name_val), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, name_val, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, ""); |
| } |
| |
| // 2. Invocation & Argument Counts |
| TEST_F(NapiV8Test, FunctionCallZeroArgs) { |
| auto zero_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 5; |
| napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr); |
| EXPECT_EQ(argc, 0u); |
| napi_value res; |
| napi_create_int32(env, 42, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "zeroArgs", NAPI_AUTO_LENGTH, zero_cb, nullptr, &fn), |
| napi_ok); |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 0, nullptr, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 42); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallSingleArg) { |
| auto double_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); |
| EXPECT_EQ(argc, 1u); |
| int32_t x = 0; |
| napi_get_value_int32(env, arg, &x); |
| napi_value res; |
| napi_create_int32(env, x * 2, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "doubleFn", NAPI_AUTO_LENGTH, double_cb, nullptr, &fn), |
| napi_ok); |
| napi_value arg; |
| ASSERT_EQ(napi_create_int32(env_, 21, &arg), napi_ok); |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 1, &arg, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 42); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallTwoArgs) { |
| auto add_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 2; |
| napi_value args[2]; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 2u); |
| int32_t a = 0, b = 0; |
| napi_get_value_int32(env, args[0], &a); |
| napi_get_value_int32(env, args[1], &b); |
| napi_value res; |
| napi_create_int32(env, a + b, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "add", NAPI_AUTO_LENGTH, add_cb, nullptr, &fn), napi_ok); |
| napi_value a, b; |
| ASSERT_EQ(napi_create_int32(env_, 15, &a), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 25, &b), napi_ok); |
| napi_value argv[2] = {a, b}; |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 2, argv, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 40); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallStringArgs) { |
| auto concat_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 2; |
| napi_value args[2] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 2u); |
| |
| char buf1[32]; |
| char buf2[32]; |
| napi_get_value_string_utf8(env, args[0], buf1, sizeof(buf1), nullptr); |
| napi_get_value_string_utf8(env, args[1], buf2, sizeof(buf2), nullptr); |
| |
| std::string combined = std::string(buf1) + buf2; |
| napi_value res; |
| napi_create_string_utf8(env, combined.c_str(), combined.length(), &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "concatFn", NAPI_AUTO_LENGTH, concat_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value s1, s2; |
| ASSERT_EQ(napi_create_string_utf8(env_, "Hello ", NAPI_AUTO_LENGTH, &s1), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "World!", NAPI_AUTO_LENGTH, &s2), napi_ok); |
| napi_value argv[2] = {s1, s2}; |
| |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 2, argv, &call_res), napi_ok); |
| char out_buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, call_res, out_buf, sizeof(out_buf), nullptr), |
| napi_ok); |
| EXPECT_STREQ(out_buf, "Hello World!"); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallBooleanAndDoubleArgs) { |
| auto calc_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 2; |
| napi_value args[2] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 2u); |
| |
| bool flag = false; |
| double factor = 0.0; |
| napi_get_value_bool(env, args[0], &flag); |
| napi_get_value_double(env, args[1], &factor); |
| |
| double result_val = flag ? (factor * 2.5) : (factor * 0.5); |
| napi_value res; |
| napi_create_double(env, result_val, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "calcFn", NAPI_AUTO_LENGTH, calc_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value factor_val; |
| ASSERT_EQ(napi_create_double(env_, 4.0, &factor_val), napi_ok); |
| |
| // Call 1: flag = true -> 4.0 * 2.5 = 10.0 |
| napi_value true_val; |
| ASSERT_EQ(napi_get_boolean(env_, true, &true_val), napi_ok); |
| napi_value argv_true[2] = {true_val, factor_val}; |
| napi_value res_true; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 2, argv_true, &res_true), napi_ok); |
| double num_true = 0.0; |
| ASSERT_EQ(napi_get_value_double(env_, res_true, &num_true), napi_ok); |
| EXPECT_DOUBLE_EQ(num_true, 10.0); |
| |
| // Call 2: flag = false -> 4.0 * 0.5 = 2.0 |
| napi_value false_val; |
| ASSERT_EQ(napi_get_boolean(env_, false, &false_val), napi_ok); |
| napi_value argv_false[2] = {false_val, factor_val}; |
| napi_value res_false; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 2, argv_false, &res_false), napi_ok); |
| double num_false = 0.0; |
| ASSERT_EQ(napi_get_value_double(env_, res_false, &num_false), napi_ok); |
| EXPECT_DOUBLE_EQ(num_false, 2.0); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallObjectArg) { |
| auto obj_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); |
| EXPECT_EQ(argc, 1u); |
| |
| napi_valuetype arg_type; |
| napi_typeof(env, arg, &arg_type); |
| EXPECT_EQ(arg_type, napi_object); |
| |
| napi_value x_val, y_val; |
| napi_get_named_property(env, arg, "x", &x_val); |
| napi_get_named_property(env, arg, "y", &y_val); |
| |
| int32_t x = 0, y = 0; |
| napi_get_value_int32(env, x_val, &x); |
| napi_get_value_int32(env, y_val, &y); |
| |
| napi_value res; |
| napi_create_int32(env, x + y, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "objFn", NAPI_AUTO_LENGTH, obj_cb, nullptr, &fn), napi_ok); |
| |
| napi_value input_obj; |
| ASSERT_EQ(napi_create_object(env_, &input_obj), napi_ok); |
| napi_value x, y; |
| ASSERT_EQ(napi_create_int32(env_, 100, &x), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 200, &y), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, input_obj, "x", x), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, input_obj, "y", y), napi_ok); |
| |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 1, &input_obj, &call_res), napi_ok); |
| int32_t sum = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &sum), napi_ok); |
| EXPECT_EQ(sum, 300); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallArrayArg) { |
| auto array_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); |
| EXPECT_EQ(argc, 1u); |
| |
| uint32_t len = 0; |
| napi_get_array_length(env, arg, &len); |
| EXPECT_EQ(len, 3u); |
| |
| int32_t total = 0; |
| for (uint32_t i = 0; i < len; ++i) { |
| napi_value elem; |
| napi_get_element(env, arg, i, &elem); |
| int32_t v = 0; |
| napi_get_value_int32(env, elem, &v); |
| total += v; |
| } |
| |
| napi_value res; |
| napi_create_int32(env, total, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "arrayFn", NAPI_AUTO_LENGTH, array_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value arr; |
| ASSERT_EQ(napi_create_array(env_, &arr), napi_ok); |
| napi_value e0, e1, e2; |
| ASSERT_EQ(napi_create_int32(env_, 10, &e0), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 20, &e1), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 30, &e2), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, arr, 0, e0), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, arr, 1, e1), napi_ok); |
| ASSERT_EQ(napi_set_element(env_, arr, 2, e2), napi_ok); |
| |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 1, &arr, &call_res), napi_ok); |
| int32_t sum = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &sum), napi_ok); |
| EXPECT_EQ(sum, 60); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallNullAndUndefinedArgs) { |
| auto null_undef_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 2; |
| napi_value args[2] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 2u); |
| |
| napi_valuetype t0, t1; |
| napi_typeof(env, args[0], &t0); |
| napi_typeof(env, args[1], &t1); |
| EXPECT_EQ(t0, napi_null); |
| EXPECT_EQ(t1, napi_undefined); |
| |
| napi_value res; |
| napi_get_boolean(env, true, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ( |
| napi_create_function(env_, "nullUndefFn", NAPI_AUTO_LENGTH, null_undef_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value null_val, undef_val; |
| ASSERT_EQ(napi_get_null(env_, &null_val), napi_ok); |
| ASSERT_EQ(napi_get_undefined(env_, &undef_val), napi_ok); |
| napi_value argv[2] = {null_val, undef_val}; |
| |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 2, argv, &call_res), napi_ok); |
| bool b_val = false; |
| ASSERT_EQ(napi_get_value_bool(env_, call_res, &b_val), napi_ok); |
| EXPECT_TRUE(b_val); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallMixedArgs) { |
| auto mixed_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 4; |
| napi_value args[4] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 4u); |
| |
| napi_valuetype t0, t1, t2, t3; |
| napi_typeof(env, args[0], &t0); |
| napi_typeof(env, args[1], &t1); |
| napi_typeof(env, args[2], &t2); |
| napi_typeof(env, args[3], &t3); |
| EXPECT_EQ(t0, napi_string); |
| EXPECT_EQ(t1, napi_number); |
| EXPECT_EQ(t2, napi_boolean); |
| EXPECT_EQ(t3, napi_object); |
| |
| char prefix[32]; |
| napi_get_value_string_utf8(env, args[0], prefix, sizeof(prefix), nullptr); |
| int32_t count = 0; |
| napi_get_value_int32(env, args[1], &count); |
| bool enabled = false; |
| napi_get_value_bool(env, args[2], &enabled); |
| |
| napi_value tag_prop; |
| napi_get_named_property(env, args[3], "tag", &tag_prop); |
| char tag_str[32]; |
| napi_get_value_string_utf8(env, tag_prop, tag_str, sizeof(tag_str), nullptr); |
| |
| std::string result_str = std::string(prefix) + ":" + std::to_string(count) + ":" + |
| (enabled ? "on" : "off") + ":" + tag_str; |
| napi_value res; |
| napi_create_string_utf8(env, result_str.c_str(), result_str.length(), &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "mixedFn", NAPI_AUTO_LENGTH, mixed_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value str_val, num_val, bool_val, obj_val, tag_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "config", NAPI_AUTO_LENGTH, &str_val), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 7, &num_val), napi_ok); |
| ASSERT_EQ(napi_get_boolean(env_, true, &bool_val), napi_ok); |
| ASSERT_EQ(napi_create_object(env_, &obj_val), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "v1", NAPI_AUTO_LENGTH, &tag_val), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj_val, "tag", tag_val), napi_ok); |
| |
| napi_value argv[4] = {str_val, num_val, bool_val, obj_val}; |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 4, argv, &call_res), napi_ok); |
| |
| char out_buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, call_res, out_buf, sizeof(out_buf), nullptr), |
| napi_ok); |
| EXPECT_STREQ(out_buf, "config:7:on:v1"); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallManyArgs) { |
| auto sum_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 5; |
| napi_value args[5] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 5u); |
| int32_t v0 = 0, v1 = 0, v2 = 0, v3 = 0, v4 = 0; |
| napi_get_value_int32(env, args[0], &v0); |
| napi_get_value_int32(env, args[1], &v1); |
| napi_get_value_int32(env, args[2], &v2); |
| napi_get_value_int32(env, args[3], &v3); |
| napi_get_value_int32(env, args[4], &v4); |
| napi_value res; |
| napi_create_int32(env, v0 + v1 + v2 + v3 + v4, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "sumFive", NAPI_AUTO_LENGTH, sum_cb, nullptr, &fn), |
| napi_ok); |
| napi_value a, b, c, d, e; |
| ASSERT_EQ(napi_create_int32(env_, 10, &a), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 20, &b), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 30, &c), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 40, &d), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 50, &e), napi_ok); |
| napi_value argv[5] = {a, b, c, d, e}; |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 5, argv, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 150); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallFewerArgsThanExpected) { |
| auto cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 3; |
| napi_value args[3] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 1u); |
| int32_t val = 0; |
| napi_get_value_int32(env, args[0], &val); |
| |
| // Missing arguments are populated with undefined |
| napi_valuetype t1, t2; |
| napi_typeof(env, args[1], &t1); |
| EXPECT_EQ(t1, napi_undefined); |
| napi_typeof(env, args[2], &t2); |
| EXPECT_EQ(t2, napi_undefined); |
| |
| napi_value res; |
| napi_create_int32(env, val * 10, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "fewerArgs", NAPI_AUTO_LENGTH, cb, nullptr, &fn), napi_ok); |
| napi_value arg; |
| ASSERT_EQ(napi_create_int32(env_, 7, &arg), napi_ok); |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 1, &arg, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 70); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallMoreArgsThanExpected) { |
| auto cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 2; |
| napi_value args[2] = {}; |
| napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| EXPECT_EQ(argc, 4u); |
| int32_t a = 0, b = 0; |
| napi_get_value_int32(env, args[0], &a); |
| napi_get_value_int32(env, args[1], &b); |
| napi_value res; |
| napi_create_int32(env, a + b, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "moreArgs", NAPI_AUTO_LENGTH, cb, nullptr, &fn), napi_ok); |
| napi_value a, b, c, d; |
| ASSERT_EQ(napi_create_int32(env_, 100, &a), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 200, &b), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 300, &c), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 400, &d), napi_ok); |
| napi_value argv[4] = {a, b, c, d}; |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 4, argv, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 300); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallbackUserData) { |
| int32_t custom_payload = 777; |
| auto cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| void* data = nullptr; |
| napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); |
| EXPECT_NE(data, nullptr); |
| int32_t payload_val = *static_cast<int32_t*>(data); |
| napi_value res; |
| napi_create_int32(env, payload_val + 1, &res); |
| return res; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "dataFn", NAPI_AUTO_LENGTH, cb, &custom_payload, &fn), |
| napi_ok); |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, nullptr, fn, 0, nullptr, &call_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, call_res, &val), napi_ok); |
| EXPECT_EQ(val, 778); |
| } |
| |
| TEST_F(NapiV8Test, FunctionCallbackThisArg) { |
| napi_value receiver; |
| ASSERT_EQ(napi_create_object(env_, &receiver), napi_ok); |
| |
| // Verify property does not exist on receiver prior to invocation |
| bool has_prop = true; |
| ASSERT_EQ(napi_has_named_property(env_, receiver, "modifiedByCallback", &has_prop), napi_ok); |
| EXPECT_FALSE(has_prop); |
| |
| auto cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| napi_value this_arg; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); |
| napi_value v; |
| napi_create_int32(env, 999, &v); |
| napi_set_named_property(env, this_arg, "modifiedByCallback", v); |
| return nullptr; |
| }; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "thisFn", NAPI_AUTO_LENGTH, cb, nullptr, &fn), napi_ok); |
| napi_value call_res; |
| ASSERT_EQ(napi_call_function(env_, receiver, fn, 0, nullptr, &call_res), napi_ok); |
| |
| // Verify property now exists on receiver with the value set by callback |
| ASSERT_EQ(napi_has_named_property(env_, receiver, "modifiedByCallback", &has_prop), napi_ok); |
| EXPECT_TRUE(has_prop); |
| |
| napi_value prop_val; |
| ASSERT_EQ(napi_get_named_property(env_, receiver, "modifiedByCallback", &prop_val), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, prop_val, &val), napi_ok); |
| EXPECT_EQ(val, 999); |
| } |
| |
| // 3. Classes & Constructors |
| TEST_F(NapiV8Test, ClassConstructor) { |
| auto ctor_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_value this_arg; |
| napi_get_cb_info(env, info, &argc, &arg, &this_arg, nullptr); |
| int32_t init_val = 0; |
| if (argc > 0) { |
| napi_get_value_int32(env, arg, &init_val); |
| } |
| napi_value v; |
| napi_create_int32(env, init_val, &v); |
| napi_set_named_property(env, this_arg, "_val", v); |
| return nullptr; |
| }; |
| |
| napi_value ctor; |
| ASSERT_EQ( |
| napi_define_class(env_, "MyClass", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_value arg; |
| ASSERT_EQ(napi_create_int32(env_, 42, &arg), napi_ok); |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 1, &arg, &inst), napi_ok); |
| |
| napi_valuetype inst_type; |
| ASSERT_EQ(napi_typeof(env_, inst, &inst_type), napi_ok); |
| EXPECT_EQ(inst_type, napi_object); |
| |
| napi_value val_prop; |
| ASSERT_EQ(napi_get_named_property(env_, inst, "_val", &val_prop), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, val_prop, &val), napi_ok); |
| EXPECT_EQ(val, 42); |
| } |
| |
| TEST_F(NapiV8Test, ClassInstanceMethods) { |
| auto ctor_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| napi_value this_arg; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); |
| napi_value v; |
| napi_create_int32(env, 10, &v); |
| napi_set_named_property(env, this_arg, "_base", v); |
| return nullptr; |
| }; |
| |
| auto multiply_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_value this_arg; |
| napi_get_cb_info(env, info, &argc, &arg, &this_arg, nullptr); |
| int32_t factor = 1; |
| if (argc > 0) { |
| napi_get_value_int32(env, arg, &factor); |
| } |
| napi_value base_prop; |
| napi_get_named_property(env, this_arg, "_base", &base_prop); |
| int32_t base = 0; |
| napi_get_value_int32(env, base_prop, &base); |
| napi_value res; |
| napi_create_int32(env, base * factor, &res); |
| return res; |
| }; |
| |
| napi_property_descriptor props[] = { |
| {"multiply", nullptr, multiply_cb, nullptr, nullptr, nullptr, napi_default, nullptr}, |
| }; |
| |
| napi_value ctor; |
| ASSERT_EQ( |
| napi_define_class(env_, "Multiplier", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 1, props, &ctor), |
| napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| napi_value method_fn; |
| ASSERT_EQ(napi_get_named_property(env_, inst, "multiply", &method_fn), napi_ok); |
| |
| napi_value factor_arg; |
| ASSERT_EQ(napi_create_int32(env_, 5, &factor_arg), napi_ok); |
| napi_value method_res; |
| ASSERT_EQ(napi_call_function(env_, inst, method_fn, 1, &factor_arg, &method_res), napi_ok); |
| int32_t res_val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, method_res, &res_val), napi_ok); |
| EXPECT_EQ(res_val, 50); |
| } |
| |
| TEST_F(NapiV8Test, ClassGettersAndSetters) { |
| auto ctor_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| auto getter_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| napi_value this_arg; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); |
| napi_value v; |
| napi_get_named_property(env, this_arg, "_hidden", &v); |
| return v; |
| }; |
| auto setter_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_value this_arg; |
| napi_get_cb_info(env, info, &argc, &arg, &this_arg, nullptr); |
| napi_set_named_property(env, this_arg, "_hidden", arg); |
| return nullptr; |
| }; |
| |
| napi_property_descriptor props[] = { |
| {"val", nullptr, nullptr, getter_cb, setter_cb, nullptr, napi_default, nullptr}, |
| }; |
| |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "AccessorClass", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 1, props, |
| &ctor), |
| napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| // Set val = 123 |
| napi_value new_val; |
| ASSERT_EQ(napi_create_int32(env_, 123, &new_val), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, inst, "val", new_val), napi_ok); |
| |
| // Read val via getter |
| napi_value read_val; |
| ASSERT_EQ(napi_get_named_property(env_, inst, "val", &read_val), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_val, &val), napi_ok); |
| EXPECT_EQ(val, 123); |
| } |
| |
| TEST_F(NapiV8Test, ClassStaticProperties) { |
| auto ctor_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| auto static_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value res; |
| napi_create_int32(env, 888, &res); |
| return res; |
| }; |
| |
| napi_property_descriptor props[] = { |
| {"staticMethod", nullptr, static_cb, nullptr, nullptr, nullptr, napi_static, nullptr}, |
| }; |
| |
| napi_value ctor; |
| ASSERT_EQ( |
| napi_define_class(env_, "StaticClass", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 1, props, &ctor), |
| napi_ok); |
| |
| // Static method is available on constructor function |
| napi_value static_fn; |
| ASSERT_EQ(napi_get_named_property(env_, ctor, "staticMethod", &static_fn), napi_ok); |
| napi_value static_res; |
| ASSERT_EQ(napi_call_function(env_, ctor, static_fn, 0, nullptr, &static_res), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, static_res, &val), napi_ok); |
| EXPECT_EQ(val, 888); |
| |
| // Static method is NOT on instances |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| bool has_on_inst = true; |
| ASSERT_EQ(napi_has_named_property(env_, inst, "staticMethod", &has_on_inst), napi_ok); |
| EXPECT_FALSE(has_on_inst); |
| } |
| |
| TEST_F(NapiV8Test, ClassStaticAndPrototypeValues) { |
| auto ctor_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| |
| napi_value static_val; |
| ASSERT_EQ(napi_create_int32(env_, 777, &static_val), napi_ok); |
| |
| napi_value proto_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "constant_tag", NAPI_AUTO_LENGTH, &proto_val), napi_ok); |
| |
| napi_property_descriptor props[] = { |
| {"STATIC_CONST", nullptr, nullptr, nullptr, nullptr, static_val, |
| static_cast<napi_property_attributes>(napi_static | napi_enumerable), nullptr}, |
| {"PROTO_CONST", nullptr, nullptr, nullptr, nullptr, proto_val, napi_enumerable, nullptr}, |
| }; |
| |
| napi_value ctor; |
| ASSERT_EQ( |
| napi_define_class(env_, "ConstClass", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 2, props, &ctor), |
| napi_ok); |
| |
| // Static value is on constructor |
| napi_value read_static; |
| ASSERT_EQ(napi_get_named_property(env_, ctor, "STATIC_CONST", &read_static), napi_ok); |
| int32_t s_val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_static, &s_val), napi_ok); |
| EXPECT_EQ(s_val, 777); |
| |
| // Static value is NOT on instance |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| bool has_static_on_inst = true; |
| ASSERT_EQ(napi_has_named_property(env_, inst, "STATIC_CONST", &has_static_on_inst), napi_ok); |
| EXPECT_FALSE(has_static_on_inst); |
| |
| // Prototype constant value is inherited by instance |
| napi_value read_proto; |
| ASSERT_EQ(napi_get_named_property(env_, inst, "PROTO_CONST", &read_proto), napi_ok); |
| char tag_buf[32]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_proto, tag_buf, sizeof(tag_buf), nullptr), |
| napi_ok); |
| EXPECT_STREQ(tag_buf, "constant_tag"); |
| } |
| |
| TEST_F(NapiV8Test, ClassEmptyProperties) { |
| auto ctor_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "EmptyClass", NAPI_AUTO_LENGTH, ctor_cb, nullptr, 0, nullptr, |
| &ctor), |
| napi_ok); |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| napi_valuetype inst_type; |
| ASSERT_EQ(napi_typeof(env_, inst, &inst_type), napi_ok); |
| EXPECT_EQ(inst_type, napi_object); |
| } |
| |
| // 4. Object Properties |
| TEST_F(NapiV8Test, DefineObjectMethods) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| auto method_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value res; |
| napi_create_string_utf8(env, "hello_method", NAPI_AUTO_LENGTH, &res); |
| return res; |
| }; |
| |
| napi_property_descriptor descs[] = { |
| {"myMethod", nullptr, method_cb, nullptr, nullptr, nullptr, napi_default, nullptr}, |
| }; |
| ASSERT_EQ(napi_define_properties(env_, obj, 1, descs), napi_ok); |
| |
| napi_value fn; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "myMethod", &fn), napi_ok); |
| napi_value ret; |
| ASSERT_EQ(napi_call_function(env_, obj, fn, 0, nullptr, &ret), napi_ok); |
| char buf[32]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, ret, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "hello_method"); |
| } |
| |
| TEST_F(NapiV8Test, DefineObjectValues) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| napi_value num_val; |
| ASSERT_EQ(napi_create_int32(env_, 999, &num_val), napi_ok); |
| |
| napi_property_descriptor descs[] = { |
| {"myConst", nullptr, nullptr, nullptr, nullptr, num_val, napi_default, nullptr}, |
| }; |
| ASSERT_EQ(napi_define_properties(env_, obj, 1, descs), napi_ok); |
| |
| napi_value read_val; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "myConst", &read_val), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_val, &val), napi_ok); |
| EXPECT_EQ(val, 999); |
| } |
| |
| TEST_F(NapiV8Test, DefineObjectAccessors) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| auto getter_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| napi_value this_arg; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); |
| napi_value v; |
| napi_get_named_property(env, this_arg, "_backing", &v); |
| return v; |
| }; |
| auto setter_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| size_t argc = 1; |
| napi_value arg; |
| napi_value this_arg; |
| napi_get_cb_info(env, info, &argc, &arg, &this_arg, nullptr); |
| napi_set_named_property(env, this_arg, "_backing", arg); |
| return nullptr; |
| }; |
| |
| napi_property_descriptor descs[] = { |
| {"prop", nullptr, nullptr, getter_cb, setter_cb, nullptr, napi_default, nullptr}, |
| }; |
| ASSERT_EQ(napi_define_properties(env_, obj, 1, descs), napi_ok); |
| |
| napi_value set_val; |
| ASSERT_EQ(napi_create_int32(env_, 555, &set_val), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "prop", set_val), napi_ok); |
| |
| napi_value get_val; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "prop", &get_val), napi_ok); |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, get_val, &val), napi_ok); |
| EXPECT_EQ(val, 555); |
| } |
| |
| TEST_F(NapiV8Test, DefineObjectPropertyAttributes) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_value num_val; |
| ASSERT_EQ(napi_create_int32(env_, 42, &num_val), napi_ok); |
| |
| napi_property_descriptor descs[] = { |
| {"enumProp", nullptr, nullptr, nullptr, nullptr, num_val, napi_enumerable, nullptr}, |
| {"nonEnumProp", nullptr, nullptr, nullptr, nullptr, num_val, napi_default, nullptr}, |
| }; |
| ASSERT_EQ(napi_define_properties(env_, obj, 2, descs), napi_ok); |
| |
| // Enumerable property names should contain only "enumProp" |
| napi_value enum_names; |
| ASSERT_EQ(napi_get_property_names(env_, obj, &enum_names), napi_ok); |
| uint32_t enum_len = 0; |
| ASSERT_EQ(napi_get_array_length(env_, enum_names, &enum_len), napi_ok); |
| EXPECT_EQ(enum_len, 1u); |
| |
| napi_value first_name; |
| ASSERT_EQ(napi_get_element(env_, enum_names, 0, &first_name), napi_ok); |
| char name_buf[32]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, first_name, name_buf, sizeof(name_buf), nullptr), |
| napi_ok); |
| EXPECT_STREQ(name_buf, "enumProp"); |
| |
| // Both properties can still be accessed directly by name on the object |
| napi_value val_enum, val_non_enum; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "enumProp", &val_enum), napi_ok); |
| ASSERT_EQ(napi_get_named_property(env_, obj, "nonEnumProp", &val_non_enum), napi_ok); |
| int32_t v1 = 0, v2 = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, val_enum, &v1), napi_ok); |
| ASSERT_EQ(napi_get_value_int32(env_, val_non_enum, &v2), napi_ok); |
| EXPECT_EQ(v1, 42); |
| EXPECT_EQ(v2, 42); |
| } |
| |
| TEST_F(NapiV8Test, DefineObjectWritableAttribute) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| napi_value val1, val2; |
| ASSERT_EQ(napi_create_int32(env_, 100, &val1), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 200, &val2), napi_ok); |
| |
| napi_property_descriptor descs[] = { |
| {"writableProp", nullptr, nullptr, nullptr, nullptr, val1, napi_writable, nullptr}, |
| {"readOnlyProp", nullptr, nullptr, nullptr, nullptr, val2, napi_default, nullptr}, |
| }; |
| ASSERT_EQ(napi_define_properties(env_, obj, 2, descs), napi_ok); |
| |
| // Writable property can be overwritten |
| napi_value new_val1; |
| ASSERT_EQ(napi_create_int32(env_, 999, &new_val1), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "writableProp", new_val1), napi_ok); |
| napi_value read_w; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "writableProp", &read_w), napi_ok); |
| int32_t w_res = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_w, &w_res), napi_ok); |
| EXPECT_EQ(w_res, 999); |
| |
| // Read-only property assignment in non-strict JS returns napi_ok but leaves value unchanged |
| napi_value new_val2; |
| ASSERT_EQ(napi_create_int32(env_, 888, &new_val2), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "readOnlyProp", new_val2), napi_ok); |
| napi_value read_ro; |
| ASSERT_EQ(napi_get_named_property(env_, obj, "readOnlyProp", &read_ro), napi_ok); |
| int32_t ro_res = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_ro, &ro_res), napi_ok); |
| EXPECT_EQ(ro_res, 200); // Unchanged! |
| } |
| |
| TEST_F(NapiV8Test, DefineObjectEmptyProperties) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_define_properties(env_, obj, 0, nullptr), napi_ok); |
| } |
| |
| // 5. Invalid Arguments |
| TEST_F(NapiV8Test, FunctionInvalidArgs) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_value fn; |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| ASSERT_EQ(napi_create_function(env_, "noop", NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), napi_ok); |
| |
| // napi_create_function |
| EXPECT_EQ(napi_create_function(nullptr, "f", NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_create_function(env_, "f", NAPI_AUTO_LENGTH, nullptr, nullptr, &fn), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_create_function(env_, "f", NAPI_AUTO_LENGTH, noop_cb, nullptr, nullptr), |
| napi_invalid_arg); |
| |
| // napi_call_function |
| napi_value call_res; |
| EXPECT_EQ(napi_call_function(env_, nullptr, obj, 0, nullptr, &call_res), |
| napi_function_expected); |
| EXPECT_EQ(napi_call_function(nullptr, nullptr, fn, 0, nullptr, &call_res), napi_invalid_arg); |
| EXPECT_EQ(napi_call_function(env_, nullptr, nullptr, 0, nullptr, &call_res), napi_invalid_arg); |
| |
| // napi_get_cb_info |
| EXPECT_EQ(napi_get_cb_info(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_get_cb_info(env_, nullptr, nullptr, nullptr, nullptr, nullptr), |
| napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, ClassAndPropertyInvalidArgs) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_value fn; |
| auto noop_cb = [](napi_env, napi_callback_info) -> napi_value { return nullptr; }; |
| ASSERT_EQ(napi_create_function(env_, "noop", NAPI_AUTO_LENGTH, noop_cb, nullptr, &fn), napi_ok); |
| |
| // napi_new_instance |
| napi_value inst_res; |
| EXPECT_EQ(napi_new_instance(env_, obj, 0, nullptr, &inst_res), napi_function_expected); |
| EXPECT_EQ(napi_new_instance(nullptr, fn, 0, nullptr, &inst_res), napi_invalid_arg); |
| EXPECT_EQ(napi_new_instance(env_, nullptr, 0, nullptr, &inst_res), napi_invalid_arg); |
| EXPECT_EQ(napi_new_instance(env_, fn, 0, nullptr, nullptr), napi_invalid_arg); |
| |
| // napi_define_class |
| napi_value ctor_res; |
| EXPECT_EQ( |
| napi_define_class(nullptr, "C", NAPI_AUTO_LENGTH, noop_cb, nullptr, 0, nullptr, &ctor_res), |
| napi_invalid_arg); |
| EXPECT_EQ( |
| napi_define_class(env_, "C", NAPI_AUTO_LENGTH, nullptr, nullptr, 0, nullptr, &ctor_res), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_define_class(env_, "C", NAPI_AUTO_LENGTH, noop_cb, nullptr, 0, nullptr, nullptr), |
| napi_invalid_arg); |
| EXPECT_EQ( |
| napi_define_class(env_, "C", NAPI_AUTO_LENGTH, noop_cb, nullptr, 1, nullptr, &ctor_res), |
| napi_invalid_arg); |
| |
| // napi_define_properties |
| EXPECT_EQ(napi_define_properties(nullptr, obj, 0, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_define_properties(env_, nullptr, 0, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_define_properties(env_, obj, 1, nullptr), napi_invalid_arg); |
| } |
| |
| // ============================================================================ |
| // Stage 5: Errors & Exceptions |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, CreateError) { |
| napi_value msg; |
| ASSERT_EQ(napi_create_string_utf8(env_, "Something went wrong", NAPI_AUTO_LENGTH, &msg), |
| napi_ok); |
| |
| napi_value err; |
| ASSERT_EQ(napi_create_error(env_, nullptr, msg, &err), napi_ok); |
| |
| bool is_err = false; |
| ASSERT_EQ(napi_is_error(env_, err, &is_err), napi_ok); |
| EXPECT_TRUE(is_err); |
| |
| napi_value read_msg; |
| ASSERT_EQ(napi_get_named_property(env_, err, "message", &read_msg), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_msg, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "Something went wrong"); |
| } |
| |
| TEST_F(NapiV8Test, CreateErrorWithCode) { |
| napi_value code; |
| ASSERT_EQ(napi_create_string_utf8(env_, "ERR_CUSTOM_CODE", NAPI_AUTO_LENGTH, &code), napi_ok); |
| napi_value msg; |
| ASSERT_EQ(napi_create_string_utf8(env_, "Error with code", NAPI_AUTO_LENGTH, &msg), napi_ok); |
| |
| napi_value err; |
| ASSERT_EQ(napi_create_error(env_, code, msg, &err), napi_ok); |
| |
| napi_value read_code; |
| ASSERT_EQ(napi_get_named_property(env_, err, "code", &read_code), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_code, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "ERR_CUSTOM_CODE"); |
| } |
| |
| TEST_F(NapiV8Test, CreateTypeError) { |
| napi_value msg; |
| ASSERT_EQ(napi_create_string_utf8(env_, "Invalid type provided", NAPI_AUTO_LENGTH, &msg), |
| napi_ok); |
| |
| napi_value err; |
| ASSERT_EQ(napi_create_type_error(env_, nullptr, msg, &err), napi_ok); |
| |
| bool is_err = false; |
| ASSERT_EQ(napi_is_error(env_, err, &is_err), napi_ok); |
| EXPECT_TRUE(is_err); |
| |
| napi_value read_name; |
| ASSERT_EQ(napi_get_named_property(env_, err, "name", &read_name), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_name, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "TypeError"); |
| } |
| |
| TEST_F(NapiV8Test, CreateRangeError) { |
| napi_value msg; |
| ASSERT_EQ(napi_create_string_utf8(env_, "Value out of range", NAPI_AUTO_LENGTH, &msg), napi_ok); |
| |
| napi_value err; |
| ASSERT_EQ(napi_create_range_error(env_, nullptr, msg, &err), napi_ok); |
| |
| bool is_err = false; |
| ASSERT_EQ(napi_is_error(env_, err, &is_err), napi_ok); |
| EXPECT_TRUE(is_err); |
| |
| napi_value read_name; |
| ASSERT_EQ(napi_get_named_property(env_, err, "name", &read_name), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_name, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "RangeError"); |
| } |
| |
| TEST_F(NapiV8Test, IsErrorOnNonErrorTypes) { |
| napi_value num, str, b, obj, null_v, undef_v; |
| ASSERT_EQ(napi_create_int32(env_, 123, &num), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "test", NAPI_AUTO_LENGTH, &str), napi_ok); |
| ASSERT_EQ(napi_get_boolean(env_, true, &b), napi_ok); |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_get_null(env_, &null_v), napi_ok); |
| ASSERT_EQ(napi_get_undefined(env_, &undef_v), napi_ok); |
| |
| bool is_err = true; |
| ASSERT_EQ(napi_is_error(env_, num, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| ASSERT_EQ(napi_is_error(env_, str, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| ASSERT_EQ(napi_is_error(env_, b, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| ASSERT_EQ(napi_is_error(env_, obj, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| ASSERT_EQ(napi_is_error(env_, null_v, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| ASSERT_EQ(napi_is_error(env_, undef_v, &is_err), napi_ok); |
| EXPECT_FALSE(is_err); |
| } |
| |
| TEST_F(NapiV8Test, ThrowAndCatchInCallback) { |
| auto throwing_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value msg; |
| napi_create_string_utf8(env, "Operation failed", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| napi_throw(env, err); |
| return nullptr; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "throwFn", NAPI_AUTO_LENGTH, throwing_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value call_res; |
| EXPECT_EQ(napi_call_function(env_, nullptr, fn, 0, nullptr, &call_res), napi_pending_exception); |
| |
| // Verify the caught exception is the exact Error created in the callback |
| napi_value caught_err; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &caught_err), napi_ok); |
| bool is_err = false; |
| ASSERT_EQ(napi_is_error(env_, caught_err, &is_err), napi_ok); |
| EXPECT_TRUE(is_err); |
| |
| napi_value read_msg; |
| ASSERT_EQ(napi_get_named_property(env_, caught_err, "message", &read_msg), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_msg, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "Operation failed"); |
| } |
| |
| TEST_F(NapiV8Test, IsExceptionPendingLifecycle) { |
| bool pending = true; |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_FALSE(pending); |
| |
| auto throwing_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value msg; |
| napi_create_string_utf8(env, "Pending error test", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| napi_throw(env, err); |
| return nullptr; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "throwFn", NAPI_AUTO_LENGTH, throwing_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value call_res; |
| EXPECT_EQ(napi_call_function(env_, nullptr, fn, 0, nullptr, &call_res), napi_pending_exception); |
| |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_TRUE(pending); |
| |
| napi_value caught; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &caught), napi_ok); |
| |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_FALSE(pending); |
| } |
| |
| TEST_F(NapiV8Test, ThrowCaughtByJavaScriptLeavesNoPendingException) { |
| auto throwing_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value msg; |
| napi_create_string_utf8(env, "Caught by JS", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| napi_throw(env, err); |
| return nullptr; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "throwFn", NAPI_AUTO_LENGTH, throwing_cb, nullptr, &fn), |
| napi_ok); |
| napi_value global; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, global, "throwFn", fn), napi_ok); |
| |
| napi_value script; |
| ASSERT_EQ( |
| napi_create_string_utf8(env_, |
| "(() => { try { throwFn(); return 'did not throw'; } catch (e) " |
| "{ return e.message; } })()", |
| NAPI_AUTO_LENGTH, &script), |
| napi_ok); |
| napi_value result; |
| ASSERT_EQ(napi_run_script(env_, script, &result), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, result, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "Caught by JS"); |
| |
| // Once the callback has returned and JavaScript caught the exception, nothing remains pending. |
| bool pending = true; |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_FALSE(pending); |
| } |
| |
| TEST_F(NapiV8Test, ThrowIsPendingWithinTheNativeCall) { |
| struct Observation { |
| napi_status throw_status = napi_generic_failure; |
| napi_status pending_status = napi_generic_failure; |
| bool pending_after_throw = false; |
| }; |
| Observation observation; |
| |
| auto throwing_cb = [](napi_env env, napi_callback_info info) -> napi_value { |
| void* data = nullptr; |
| napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); |
| auto* obs = static_cast<Observation*>(data); |
| |
| napi_value msg; |
| napi_create_string_utf8(env, "Thrown by native code", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| obs->throw_status = napi_throw(env, err); |
| obs->pending_status = napi_is_exception_pending(env, &obs->pending_after_throw); |
| return nullptr; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ( |
| napi_create_function(env_, "throwFn", NAPI_AUTO_LENGTH, throwing_cb, &observation, &fn), |
| napi_ok); |
| napi_value global; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, global, "throwFn", fn), napi_ok); |
| |
| napi_value script; |
| ASSERT_EQ(napi_create_string_utf8(env_, |
| "(() => { try { throwFn(); return 'did not throw'; } " |
| "catch (e) { return e.message; } })()", |
| NAPI_AUTO_LENGTH, &script), |
| napi_ok); |
| napi_value result; |
| ASSERT_EQ(napi_run_script(env_, script, &result), napi_ok); |
| |
| EXPECT_EQ(observation.throw_status, napi_ok); |
| EXPECT_EQ(observation.pending_status, napi_ok); |
| EXPECT_TRUE(observation.pending_after_throw); |
| |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, result, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "Thrown by native code"); |
| } |
| |
| // Clearing a pending exception with napi_get_and_clear_last_exception() before the native callback |
| // returns stops it from being thrown into JavaScript. |
| TEST_F(NapiV8Test, ClearingThePendingExceptionStopsItReachingJavaScript) { |
| auto withdrawing_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value msg; |
| napi_create_string_utf8(env, "withdrawn", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| napi_throw(env, err); |
| |
| bool pending = false; |
| napi_is_exception_pending(env, &pending); |
| if (!pending) { |
| napi_value not_pending; |
| napi_create_string_utf8(env, "not pending", NAPI_AUTO_LENGTH, ¬_pending); |
| return not_pending; |
| } |
| |
| napi_value caught; |
| napi_get_and_clear_last_exception(env, &caught); |
| napi_value message; |
| napi_get_named_property(env, caught, "message", &message); |
| return message; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ( |
| napi_create_function(env_, "withdrawFn", NAPI_AUTO_LENGTH, withdrawing_cb, nullptr, &fn), |
| napi_ok); |
| napi_value global; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, global, "withdrawFn", fn), napi_ok); |
| |
| napi_value script; |
| ASSERT_EQ(napi_create_string_utf8(env_, |
| "(() => { try { return withdrawFn(); } " |
| "catch (e) { return 'threw: ' + e.message; } })()", |
| NAPI_AUTO_LENGTH, &script), |
| napi_ok); |
| napi_value result; |
| ASSERT_EQ(napi_run_script(env_, script, &result), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, result, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "withdrawn"); |
| |
| bool pending = true; |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_FALSE(pending); |
| } |
| |
| TEST_F(NapiV8Test, GetAndClearLastException) { |
| // When no exception has occurred, get_and_clear returns undefined |
| napi_value no_err; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &no_err), napi_ok); |
| napi_valuetype t; |
| ASSERT_EQ(napi_typeof(env_, no_err, &t), napi_ok); |
| EXPECT_EQ(t, napi_undefined); |
| |
| auto throwing_cb = [](napi_env env, napi_callback_info) -> napi_value { |
| napi_value msg; |
| napi_create_string_utf8(env, "Recoverable error", NAPI_AUTO_LENGTH, &msg); |
| napi_value err; |
| napi_create_error(env, nullptr, msg, &err); |
| napi_throw(env, err); |
| return nullptr; |
| }; |
| |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "throwFn", NAPI_AUTO_LENGTH, throwing_cb, nullptr, &fn), |
| napi_ok); |
| |
| napi_value call_res; |
| EXPECT_EQ(napi_call_function(env_, nullptr, fn, 0, nullptr, &call_res), napi_pending_exception); |
| |
| napi_value caught; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &caught), napi_ok); |
| bool is_err = false; |
| ASSERT_EQ(napi_is_error(env_, caught, &is_err), napi_ok); |
| EXPECT_TRUE(is_err); |
| |
| napi_value read_msg; |
| ASSERT_EQ(napi_get_named_property(env_, caught, "message", &read_msg), napi_ok); |
| char buf[64]; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, read_msg, buf, sizeof(buf), nullptr), napi_ok); |
| EXPECT_STREQ(buf, "Recoverable error"); |
| |
| // Subsequent call to get_and_clear returns undefined again |
| napi_value after_clear; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &after_clear), napi_ok); |
| ASSERT_EQ(napi_typeof(env_, after_clear, &t), napi_ok); |
| EXPECT_EQ(t, napi_undefined); |
| } |
| |
| TEST_F(NapiV8Test, ErrorInvalidArguments) { |
| napi_value msg, num, err, res; |
| ASSERT_EQ(napi_create_string_utf8(env_, "msg", NAPI_AUTO_LENGTH, &msg), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 123, &num), napi_ok); |
| ASSERT_EQ(napi_create_error(env_, nullptr, msg, &err), napi_ok); |
| |
| // napi_create_error |
| EXPECT_EQ(napi_create_error(nullptr, nullptr, msg, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_error(env_, nullptr, nullptr, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_error(env_, nullptr, msg, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_error(env_, nullptr, num, &res), napi_string_expected); |
| |
| // napi_create_type_error |
| EXPECT_EQ(napi_create_type_error(nullptr, nullptr, msg, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_type_error(env_, nullptr, nullptr, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_type_error(env_, nullptr, msg, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_type_error(env_, nullptr, num, &res), napi_string_expected); |
| |
| // napi_create_range_error |
| EXPECT_EQ(napi_create_range_error(nullptr, nullptr, msg, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_range_error(env_, nullptr, nullptr, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_create_range_error(env_, nullptr, msg, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_range_error(env_, nullptr, num, &res), napi_string_expected); |
| |
| // napi_throw |
| EXPECT_EQ(napi_throw(nullptr, err), napi_invalid_arg); |
| EXPECT_EQ(napi_throw(env_, nullptr), napi_invalid_arg); |
| |
| // napi_is_exception_pending |
| bool pending = false; |
| EXPECT_EQ(napi_is_exception_pending(nullptr, &pending), napi_invalid_arg); |
| EXPECT_EQ(napi_is_exception_pending(env_, nullptr), napi_invalid_arg); |
| |
| // napi_get_and_clear_last_exception |
| EXPECT_EQ(napi_get_and_clear_last_exception(nullptr, &res), napi_invalid_arg); |
| EXPECT_EQ(napi_get_and_clear_last_exception(env_, nullptr), napi_invalid_arg); |
| |
| // napi_is_error |
| bool is_err = false; |
| EXPECT_EQ(napi_is_error(nullptr, err, &is_err), napi_invalid_arg); |
| EXPECT_EQ(napi_is_error(env_, nullptr, &is_err), napi_invalid_arg); |
| EXPECT_EQ(napi_is_error(env_, err, nullptr), napi_invalid_arg); |
| } |
| |
| // ============================================================================ |
| // Stage 6: References, ObjectWrap & Instance Data |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, ReferenceLifecycleStrong) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| napi_value val; |
| ASSERT_EQ(napi_create_int32(env_, 789, &val), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "id", val), napi_ok); |
| |
| napi_ref ref = nullptr; |
| ASSERT_EQ(napi_create_reference(env_, obj, 1, &ref), napi_ok); |
| ASSERT_NE(ref, nullptr); |
| |
| napi_value fetched_obj; |
| ASSERT_EQ(napi_get_reference_value(env_, ref, &fetched_obj), napi_ok); |
| ASSERT_NE(fetched_obj, nullptr); |
| |
| napi_value read_id; |
| ASSERT_EQ(napi_get_named_property(env_, fetched_obj, "id", &read_id), napi_ok); |
| int32_t id_num = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, read_id, &id_num), napi_ok); |
| EXPECT_EQ(id_num, 789); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, ReferenceRefAndUnref) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| napi_ref ref = nullptr; |
| ASSERT_EQ(napi_create_reference(env_, obj, 0, &ref), napi_ok); |
| ASSERT_NE(ref, nullptr); |
| |
| uint32_t count = 0; |
| ASSERT_EQ(napi_reference_ref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 1u); |
| |
| ASSERT_EQ(napi_reference_ref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 2u); |
| |
| ASSERT_EQ(napi_reference_unref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 1u); |
| |
| ASSERT_EQ(napi_reference_unref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 0u); |
| |
| // Unreferencing when count is already 0 should fail |
| EXPECT_EQ(napi_reference_unref(env_, ref, &count), napi_generic_failure); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| } |
| |
| namespace { |
| struct TestNativeData { |
| int value = 0; |
| }; |
| |
| napi_value ConstructorWithInternalField(napi_env env, napi_callback_info info) { |
| napi_value this_arg; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); |
| return this_arg; |
| } |
| } // namespace |
| |
| TEST_F(NapiV8Test, ObjectWrapAndUnwrap) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "WrapTestClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| TestNativeData native_data{1234}; |
| ASSERT_EQ(napi_wrap(env_, inst, &native_data, nullptr, nullptr, nullptr), napi_ok); |
| |
| // Wrapping an already-wrapped object must fail |
| TestNativeData second_data{5678}; |
| EXPECT_EQ(napi_wrap(env_, inst, &second_data, nullptr, nullptr, nullptr), napi_invalid_arg); |
| |
| void* unwrapped = nullptr; |
| ASSERT_EQ(napi_unwrap(env_, inst, &unwrapped), napi_ok); |
| ASSERT_EQ(unwrapped, &native_data); |
| |
| TestNativeData* typed_data = static_cast<TestNativeData*>(unwrapped); |
| EXPECT_EQ(typed_data->value, 1234); |
| } |
| |
| TEST_F(NapiV8Test, ObjectRemoveWrap) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "RemoveWrapTestClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| TestNativeData native_data{5678}; |
| ASSERT_EQ(napi_wrap(env_, inst, &native_data, nullptr, nullptr, nullptr), napi_ok); |
| |
| void* removed_data = nullptr; |
| ASSERT_EQ(napi_remove_wrap(env_, inst, &removed_data), napi_ok); |
| EXPECT_EQ(removed_data, &native_data); |
| |
| // Once the wrap has been removed the object is no longer wrapped, so unwrapping it fails |
| // rather than reporting a null native object. |
| void* unwrap_after_remove = nullptr; |
| EXPECT_EQ(napi_unwrap(env_, inst, &unwrap_after_remove), napi_invalid_arg); |
| EXPECT_EQ(napi_remove_wrap(env_, inst, &unwrap_after_remove), napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, InstanceData) { |
| TestNativeData sample{999}; |
| ASSERT_EQ(napi_set_instance_data(env_, &sample, nullptr, nullptr), napi_ok); |
| |
| void* fetched = nullptr; |
| ASSERT_EQ(napi_get_instance_data(env_, &fetched), napi_ok); |
| EXPECT_EQ(fetched, &sample); |
| |
| TestNativeData* typed = static_cast<TestNativeData*>(fetched); |
| EXPECT_EQ(typed->value, 999); |
| } |
| |
| namespace { |
| void TestInstanceDataFinalizer(napi_env, void* finalize_data, void* finalize_hint) { |
| int* target = static_cast<int*>(finalize_data); |
| int increment = *static_cast<int*>(finalize_hint); |
| *target += increment; |
| } |
| } // namespace |
| |
| TEST_F(NapiV8Test, InstanceDataFinalizerOnEnvDestruction) { |
| napi_env sub_env = CreateSubEnv(); |
| int state = 10; |
| int hint = 5; |
| ASSERT_EQ(napi_set_instance_data(sub_env, &state, TestInstanceDataFinalizer, &hint), napi_ok); |
| |
| DestroySubEnv(sub_env); |
| EXPECT_EQ(state, 15); |
| } |
| |
| TEST_F(NapiV8Test, ReferencesAndWrapInvalidArguments) { |
| napi_value obj, num; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 100, &num), napi_ok); |
| |
| // napi_create_reference |
| napi_ref ref = nullptr; |
| EXPECT_EQ(napi_create_reference(nullptr, obj, 1, &ref), napi_invalid_arg); |
| EXPECT_EQ(napi_create_reference(env_, nullptr, 1, &ref), napi_invalid_arg); |
| EXPECT_EQ(napi_create_reference(env_, obj, 1, nullptr), napi_invalid_arg); |
| |
| ASSERT_EQ(napi_create_reference(env_, obj, 1, &ref), napi_ok); |
| |
| // napi_delete_reference |
| EXPECT_EQ(napi_delete_reference(nullptr, ref), napi_invalid_arg); |
| EXPECT_EQ(napi_delete_reference(env_, nullptr), napi_invalid_arg); |
| |
| // napi_reference_ref & unref |
| uint32_t count = 0; |
| EXPECT_EQ(napi_reference_ref(nullptr, ref, &count), napi_invalid_arg); |
| EXPECT_EQ(napi_reference_ref(env_, nullptr, &count), napi_invalid_arg); |
| EXPECT_EQ(napi_reference_unref(nullptr, ref, &count), napi_invalid_arg); |
| EXPECT_EQ(napi_reference_unref(env_, nullptr, &count), napi_invalid_arg); |
| |
| // napi_get_reference_value |
| napi_value get_val; |
| EXPECT_EQ(napi_get_reference_value(nullptr, ref, &get_val), napi_invalid_arg); |
| EXPECT_EQ(napi_get_reference_value(env_, nullptr, &get_val), napi_invalid_arg); |
| EXPECT_EQ(napi_get_reference_value(env_, ref, nullptr), napi_invalid_arg); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| |
| // napi_wrap argument validation. Any object can be wrapped, not just an instance of a class |
| // declared with napi_define_class(). |
| int sample_native = 0; |
| EXPECT_EQ(napi_wrap(nullptr, obj, &sample_native, nullptr, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_wrap(env_, nullptr, &sample_native, nullptr, nullptr, nullptr), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_wrap(env_, num, &sample_native, nullptr, nullptr, nullptr), |
| napi_object_expected); |
| EXPECT_EQ(napi_wrap(env_, obj, &sample_native, nullptr, nullptr, nullptr), napi_ok); |
| |
| // napi_unwrap & remove_wrap |
| void* unwrap_res = nullptr; |
| EXPECT_EQ(napi_unwrap(nullptr, obj, &unwrap_res), napi_invalid_arg); |
| EXPECT_EQ(napi_unwrap(env_, nullptr, &unwrap_res), napi_invalid_arg); |
| EXPECT_EQ(napi_unwrap(env_, obj, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_remove_wrap(nullptr, obj, &unwrap_res), napi_invalid_arg); |
| EXPECT_EQ(napi_remove_wrap(env_, nullptr, &unwrap_res), napi_invalid_arg); |
| |
| // instance data |
| void* inst_data = nullptr; |
| EXPECT_EQ(napi_set_instance_data(nullptr, &sample_native, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_get_instance_data(nullptr, &inst_data), napi_invalid_arg); |
| EXPECT_EQ(napi_get_instance_data(env_, nullptr), napi_invalid_arg); |
| } |
| |
| struct FinalizerStateTracker { |
| int call_count = 0; |
| bool finalizer_called = false; |
| bool ref_deleted = false; |
| napi_ref ref = nullptr; |
| }; |
| |
| void FinalizerDeletingReference(napi_env env, void* finalize_data, void*) { |
| auto* tracker = static_cast<FinalizerStateTracker*>(finalize_data); |
| tracker->call_count++; |
| tracker->finalizer_called = true; |
| if (tracker->ref != nullptr) { |
| if (napi_delete_reference(env, tracker->ref) == napi_ok) { |
| tracker->ref_deleted = true; |
| } |
| } |
| } |
| |
| TEST_F(NapiV8Test, ObjectWrapFinalizerDeletesRef) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "FinalizeClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| ASSERT_EQ( |
| napi_wrap(env_, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| ASSERT_NE(tracker.ref, nullptr); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC to reclaim the unreachable instance |
| RequestGC(); |
| |
| EXPECT_TRUE(tracker.finalizer_called); |
| EXPECT_TRUE(tracker.ref_deleted); |
| } |
| |
| TEST_F(NapiV8Test, DeleteReferenceDoesNotCallFinalizer) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "DeleteRefClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| ASSERT_EQ(napi_wrap(env_, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| ASSERT_NE(tracker.ref, nullptr); |
| |
| // Explicitly deleting the reference must NOT invoke the finalizer |
| ASSERT_EQ(napi_delete_reference(env_, tracker.ref), napi_ok); |
| EXPECT_FALSE(tracker.finalizer_called); |
| } |
| |
| TEST_F(NapiV8Test, EnvDestructionCallsFinalizerWithDeleteRef) { |
| napi_env sub_env = CreateSubEnv(); |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(sub_env, "TeardownRefClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(sub_env, ctor, 0, nullptr, &inst), napi_ok); |
| ASSERT_EQ(napi_wrap(sub_env, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| |
| // Create an extra strong and weak reference that are intentionally left open |
| napi_ref strong_ref = nullptr; |
| ASSERT_EQ(napi_create_reference(sub_env, inst, 1, &strong_ref), napi_ok); |
| napi_ref weak_ref = nullptr; |
| ASSERT_EQ(napi_create_reference(sub_env, inst, 0, &weak_ref), napi_ok); |
| |
| // Destroy sub_env without deleting references beforehand |
| DestroySubEnv(sub_env); |
| |
| // Finalizer must have been called during environment teardown, and ref deleted inside |
| EXPECT_TRUE(tracker.finalizer_called); |
| EXPECT_TRUE(tracker.ref_deleted); |
| EXPECT_EQ(tracker.call_count, 1); |
| |
| // Force GC after env destruction. Handles were reset during teardown, so no dangling callbacks |
| RequestGC(); |
| |
| // Verify tracker state has not changed after GC |
| EXPECT_TRUE(tracker.finalizer_called); |
| EXPECT_TRUE(tracker.ref_deleted); |
| EXPECT_EQ(tracker.call_count, 1); |
| } |
| |
| TEST_F(NapiV8Test, EnvDestructionCallsFinalizerWithoutDeleteRef) { |
| napi_env sub_env = CreateSubEnv(); |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(sub_env, "TeardownRefNoDeleteClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| int finalize_call_count = 0; |
| auto simple_finalizer = [](napi_env, void* finalize_data, void*) { |
| auto* count = static_cast<int*>(finalize_data); |
| (*count)++; |
| }; |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(sub_env, ctor, 0, nullptr, &inst), napi_ok); |
| // Wrap with nullptr result ref so user does not delete the ref |
| ASSERT_EQ(napi_wrap(sub_env, inst, &finalize_call_count, simple_finalizer, nullptr, nullptr), |
| napi_ok); |
| |
| // Destroy sub_env |
| DestroySubEnv(sub_env); |
| |
| // Finalizer must have been called exactly once during environment teardown |
| EXPECT_EQ(finalize_call_count, 1); |
| |
| RequestGC(); |
| EXPECT_EQ(finalize_call_count, 1); |
| } |
| |
| TEST_F(NapiV8Test, StrongReferenceKeepsWeakReferenceAlive) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "KeepAliveClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_ref strong_ref = nullptr; |
| napi_ref weak_ref = nullptr; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| ASSERT_EQ(napi_create_reference(env_, inst, 1, &strong_ref), napi_ok); |
| ASSERT_EQ(napi_create_reference(env_, inst, 0, &weak_ref), napi_ok); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: because strong_ref (count 1) exists, the object must NOT be collected |
| RequestGC(); |
| |
| napi_value strong_val = nullptr; |
| napi_value weak_val = nullptr; |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| ASSERT_EQ(napi_get_reference_value(env_, strong_ref, &strong_val), napi_ok); |
| EXPECT_NE(strong_val, nullptr); |
| |
| ASSERT_EQ(napi_get_reference_value(env_, weak_ref, &weak_val), napi_ok); |
| EXPECT_NE(weak_val, nullptr); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| ASSERT_EQ(napi_delete_reference(env_, strong_ref), napi_ok); |
| ASSERT_EQ(napi_delete_reference(env_, weak_ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, MultipleWeakReferencesReclaimed) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "MultipleWeakRefClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_ref ref1 = nullptr; |
| napi_ref ref2 = nullptr; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| // Both references start at count 0 (weak) |
| ASSERT_EQ(napi_create_reference(env_, inst, 0, &ref1), napi_ok); |
| ASSERT_EQ(napi_create_reference(env_, inst, 0, &ref2), napi_ok); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: with both references starting at count 0, the object must be collected |
| RequestGC(); |
| |
| napi_value val1 = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, ref1, &val1), napi_ok); |
| EXPECT_EQ(val1, nullptr); |
| |
| napi_value val2 = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, ref2, &val2), napi_ok); |
| EXPECT_EQ(val2, nullptr); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref1), napi_ok); |
| ASSERT_EQ(napi_delete_reference(env_, ref2), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, ReferenceWeakPromotionToStrongSurvivesGC) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "PromoClass", NAPI_AUTO_LENGTH, ConstructorWithInternalField, |
| nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_ref ref = nullptr; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| // Start as weak (count = 0) |
| ASSERT_EQ(napi_create_reference(env_, inst, 0, &ref), napi_ok); |
| |
| // Promote to strong (0 -> 1) before closing scope |
| uint32_t count = 0; |
| ASSERT_EQ(napi_reference_ref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 1u); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: because it was promoted to strong, the object must survive |
| RequestGC(); |
| |
| napi_value val = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, ref, &val), napi_ok); |
| EXPECT_NE(val, nullptr); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, ReferenceDemotionToWeakAllowsGC) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "DemoClass", NAPI_AUTO_LENGTH, ConstructorWithInternalField, |
| nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| napi_ref ref = nullptr; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| // Start as strong (count = 1) |
| ASSERT_EQ(napi_create_reference(env_, inst, 1, &ref), napi_ok); |
| |
| // Demote to weak (1 -> 0) |
| uint32_t count = 0; |
| ASSERT_EQ(napi_reference_unref(env_, ref, &count), napi_ok); |
| EXPECT_EQ(count, 0u); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: object must now be collected |
| RequestGC(); |
| |
| napi_value val = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, ref, &val), napi_ok); |
| EXPECT_EQ(val, nullptr); |
| |
| ASSERT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, ObjectWrapInteractingWithExternalReferences) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "WrapInteractClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| napi_ref strong_ref = nullptr; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| |
| // Wrap with a finalizer (holds an internal weak reference) |
| ASSERT_EQ( |
| napi_wrap(env_, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| |
| // Also create an independent strong reference (count = 1) |
| ASSERT_EQ(napi_create_reference(env_, inst, 1, &strong_ref), napi_ok); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: instance is kept alive by strong_ref, so finalizer must NOT be called |
| RequestGC(); |
| EXPECT_FALSE(tracker.finalizer_called); |
| |
| // Unref strong_ref (1 -> 0): now only weak references exist |
| uint32_t count = 0; |
| ASSERT_EQ(napi_reference_unref(env_, strong_ref, &count), napi_ok); |
| EXPECT_EQ(count, 0u); |
| |
| // Force GC: instance is collected and finalizer IS called |
| RequestGC(); |
| EXPECT_TRUE(tracker.finalizer_called); |
| EXPECT_TRUE(tracker.ref_deleted); |
| |
| ASSERT_EQ(napi_delete_reference(env_, strong_ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, RemoveWrapClearsFinalizerForUserlandRef) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "RemoveWrapUserClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| ASSERT_EQ( |
| napi_wrap(env_, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| ASSERT_NE(tracker.ref, nullptr); |
| |
| // Remove the wrap before the instance is GC'd |
| void* unwrapped = nullptr; |
| ASSERT_EQ(napi_remove_wrap(env_, inst, &unwrapped), napi_ok); |
| EXPECT_EQ(unwrapped, &tracker); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: instance is collected, but because wrap was removed, finalizer must NOT run |
| RequestGC(); |
| EXPECT_FALSE(tracker.finalizer_called); |
| |
| // Userland reference handle is still valid to query or delete |
| napi_value val = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, tracker.ref, &val), napi_ok); |
| EXPECT_EQ(val, nullptr); |
| |
| ASSERT_EQ(napi_delete_reference(env_, tracker.ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, RemoveWrapDeletesInternalRef) { |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(env_, "RemoveWrapInternalClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor, 0, nullptr, &inst), napi_ok); |
| // Wrap with nullptr result ref (internal runtime ref) |
| ASSERT_EQ(napi_wrap(env_, inst, &tracker, FinalizerDeletingReference, nullptr, nullptr), |
| napi_ok); |
| |
| // Remove wrap |
| void* unwrapped = nullptr; |
| ASSERT_EQ(napi_remove_wrap(env_, inst, &unwrapped), napi_ok); |
| EXPECT_EQ(unwrapped, &tracker); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| // Force GC: instance collected, finalizer must NOT run |
| RequestGC(); |
| EXPECT_FALSE(tracker.finalizer_called); |
| } |
| |
| TEST_F(NapiV8Test, RemoveWrapAndEnvDestruction) { |
| napi_env sub_env = CreateSubEnv(); |
| napi_value ctor; |
| ASSERT_EQ(napi_define_class(sub_env, "RemoveWrapTeardownClass", NAPI_AUTO_LENGTH, |
| ConstructorWithInternalField, nullptr, 0, nullptr, &ctor), |
| napi_ok); |
| |
| FinalizerStateTracker tracker; |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(sub_env, ctor, 0, nullptr, &inst), napi_ok); |
| ASSERT_EQ(napi_wrap(sub_env, inst, &tracker, FinalizerDeletingReference, nullptr, &tracker.ref), |
| napi_ok); |
| |
| void* unwrapped = nullptr; |
| ASSERT_EQ(napi_remove_wrap(sub_env, inst, &unwrapped), napi_ok); |
| EXPECT_EQ(unwrapped, &tracker); |
| |
| // Destroy sub_env without deleting tracker.ref beforehand |
| DestroySubEnv(sub_env); |
| |
| // Teardown must NOT call finalizer because wrap was removed |
| EXPECT_FALSE(tracker.finalizer_called); |
| } |
| |
| // ============================================================================ |
| // Stage 7: Buffers, TypedArrays, DataViews & Promises |
| // ============================================================================ |
| |
| TEST_F(NapiV8Test, ArrayBufferCreateAndInfo) { |
| napi_value ab; |
| void* data = nullptr; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 16, &data, &ab), napi_ok); |
| ASSERT_NE(ab, nullptr); |
| ASSERT_NE(data, nullptr); |
| |
| bool is_ab = false; |
| ASSERT_EQ(napi_is_arraybuffer(env_, ab, &is_ab), napi_ok); |
| EXPECT_TRUE(is_ab); |
| |
| napi_value num; |
| ASSERT_EQ(napi_create_int32(env_, 42, &num), napi_ok); |
| ASSERT_EQ(napi_is_arraybuffer(env_, num, &is_ab), napi_ok); |
| EXPECT_FALSE(is_ab); |
| |
| // Write bytes into data pointer |
| auto* bytes = static_cast<uint8_t*>(data); |
| for (uint8_t i = 0; i < 16; ++i) { |
| bytes[i] = i * 10; |
| } |
| |
| void* info_data = nullptr; |
| size_t byte_length = 0; |
| ASSERT_EQ(napi_get_arraybuffer_info(env_, ab, &info_data, &byte_length), napi_ok); |
| EXPECT_EQ(info_data, data); |
| EXPECT_EQ(byte_length, 16u); |
| |
| auto* read_bytes = static_cast<const uint8_t*>(info_data); |
| for (uint8_t i = 0; i < 16; ++i) { |
| EXPECT_EQ(read_bytes[i], i * 10); |
| } |
| } |
| |
| TEST_F(NapiV8Test, ArrayBufferZeroLength) { |
| napi_value ab; |
| void* data = nullptr; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 0, &data, &ab), napi_ok); |
| ASSERT_NE(ab, nullptr); |
| |
| bool is_ab = false; |
| ASSERT_EQ(napi_is_arraybuffer(env_, ab, &is_ab), napi_ok); |
| EXPECT_TRUE(is_ab); |
| |
| void* info_data = nullptr; |
| size_t byte_length = 999; |
| ASSERT_EQ(napi_get_arraybuffer_info(env_, ab, &info_data, &byte_length), napi_ok); |
| EXPECT_EQ(byte_length, 0u); |
| } |
| |
| TEST_F(NapiV8Test, ArrayBufferDetach) { |
| napi_value ab; |
| void* data = nullptr; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 32, &data, &ab), napi_ok); |
| |
| size_t byte_length = 0; |
| ASSERT_EQ(napi_get_arraybuffer_info(env_, ab, nullptr, &byte_length), napi_ok); |
| EXPECT_EQ(byte_length, 32u); |
| |
| ASSERT_EQ(napi_detach_arraybuffer(env_, ab), napi_ok); |
| |
| // After detaching, the buffer byte length drops to 0 |
| ASSERT_EQ(napi_get_arraybuffer_info(env_, ab, nullptr, &byte_length), napi_ok); |
| EXPECT_EQ(byte_length, 0u); |
| } |
| |
| TEST_F(NapiV8Test, ExternalArrayBufferHandling) { |
| auto finalizer = [](napi_env, void*, void*) {}; |
| uint8_t raw_mem[64]; |
| napi_value ab = nullptr; |
| napi_status expected_status = V8_ENABLE_SANDBOX ? napi_no_external_buffers_allowed : napi_ok; |
| EXPECT_EQ(napi_create_external_arraybuffer(env_, raw_mem, 64, finalizer, nullptr, &ab), |
| expected_status); |
| } |
| |
| TEST_F(NapiV8Test, TypedArrayOptionalOutParams) { |
| napi_value ab = nullptr; |
| void* data = nullptr; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 16, &data, &ab), napi_ok); |
| |
| napi_value ta = nullptr; |
| ASSERT_EQ(napi_create_typedarray(env_, napi_uint16_array, 4, ab, 4, &ta), napi_ok); |
| |
| napi_typedarray_type type; |
| ASSERT_EQ(napi_get_typedarray_info(env_, ta, &type, nullptr, nullptr, nullptr, nullptr), |
| napi_ok); |
| EXPECT_EQ(type, napi_uint16_array); |
| |
| size_t length = 0; |
| ASSERT_EQ(napi_get_typedarray_info(env_, ta, nullptr, &length, nullptr, nullptr, nullptr), |
| napi_ok); |
| EXPECT_EQ(length, 4u); |
| |
| void* out_data = nullptr; |
| ASSERT_EQ(napi_get_typedarray_info(env_, ta, nullptr, nullptr, &out_data, nullptr, nullptr), |
| napi_ok); |
| EXPECT_EQ(out_data, static_cast<char*>(data) + 4); |
| |
| size_t byte_offset = 0; |
| ASSERT_EQ(napi_get_typedarray_info(env_, ta, nullptr, nullptr, nullptr, nullptr, &byte_offset), |
| napi_ok); |
| EXPECT_EQ(byte_offset, 4u); |
| } |
| |
| struct TypedArrayTestCase { |
| const char* name; |
| napi_typedarray_type type; |
| size_t element_size; |
| }; |
| |
| class TypedArrayAllTypes : public NapiV8Test, |
| public ::testing::WithParamInterface<TypedArrayTestCase> {}; |
| |
| TEST_P(TypedArrayAllTypes, CorrectTypeAndDimensions) { |
| const auto& test_case = GetParam(); |
| napi_value buffer_64 = nullptr; |
| void* buffer_64_data = nullptr; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 64, &buffer_64_data, &buffer_64), napi_ok); |
| |
| napi_value ta = nullptr; |
| size_t offset = test_case.element_size; |
| size_t count = 3; |
| ASSERT_EQ(napi_create_typedarray(env_, test_case.type, count, buffer_64, offset, &ta), napi_ok); |
| |
| bool is_ta = false; |
| ASSERT_EQ(napi_is_typedarray(env_, ta, &is_ta), napi_ok); |
| EXPECT_TRUE(is_ta); |
| |
| napi_typedarray_type detected_type; |
| size_t detected_length = 0; |
| void* detected_data = nullptr; |
| napi_value detected_ab = nullptr; |
| size_t detected_offset = 0; |
| ASSERT_EQ(napi_get_typedarray_info(env_, ta, &detected_type, &detected_length, &detected_data, |
| &detected_ab, &detected_offset), |
| napi_ok); |
| EXPECT_EQ(detected_type, test_case.type); |
| EXPECT_EQ(detected_length, count); |
| EXPECT_EQ(detected_offset, offset); |
| EXPECT_EQ(detected_data, static_cast<char*>(buffer_64_data) + offset); |
| ASSERT_NE(detected_ab, nullptr); |
| |
| bool is_ab = false; |
| ASSERT_EQ(napi_is_arraybuffer(env_, detected_ab, &is_ab), napi_ok); |
| EXPECT_TRUE(is_ab); |
| } |
| |
| std::string TypedArrayTestName(const ::testing::TestParamInfo<TypedArrayTestCase>& info) { |
| return info.param.name; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| AllTypes, |
| TypedArrayAllTypes, |
| ::testing::Values(TypedArrayTestCase{"Int8", napi_int8_array, 1}, |
| TypedArrayTestCase{"Uint8", napi_uint8_array, 1}, |
| TypedArrayTestCase{"Uint8Clamped", napi_uint8_clamped_array, 1}, |
| TypedArrayTestCase{"Int16", napi_int16_array, 2}, |
| TypedArrayTestCase{"Uint16", napi_uint16_array, 2}, |
| TypedArrayTestCase{"Int32", napi_int32_array, 4}, |
| TypedArrayTestCase{"Uint32", napi_uint32_array, 4}, |
| TypedArrayTestCase{"Float32", napi_float32_array, 4}, |
| TypedArrayTestCase{"Float64", napi_float64_array, 8}, |
| TypedArrayTestCase{"BigInt64", napi_bigint64_array, 8}, |
| TypedArrayTestCase{"BigUint64", napi_biguint64_array, 8}), |
| TypedArrayTestName); |
| |
| TEST_F(NapiV8Test, PromiseResolve) { |
| // napi_create_promise creates a JavaScript Promise along with an opaque napi_deferred |
| // handle used by C++ to settle (resolve or reject) the promise. |
| napi_deferred deferred = nullptr; |
| napi_value promise = nullptr; |
| ASSERT_EQ(napi_create_promise(env_, &deferred, &promise), napi_ok); |
| ASSERT_NE(deferred, nullptr); |
| ASSERT_NE(promise, nullptr); |
| |
| bool is_prom = false; |
| ASSERT_EQ(napi_is_promise(env_, promise, &is_prom), napi_ok); |
| EXPECT_TRUE(is_prom); |
| |
| // Node-API does not expose an API to directly inspect the resolution state or value of a |
| // promise from C++. To observe resolution, expose the promise to JavaScript and attach a |
| // standard .then() callback. |
| napi_value global; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, global, "testPromise", promise), napi_ok); |
| |
| napi_value script_res; |
| napi_value script_src; |
| const char* setup_script = |
| "globalThis.resolvedVal = 0;\n" |
| "testPromise.then(val => { globalThis.resolvedVal = val; });"; |
| ASSERT_EQ(napi_create_string_utf8(env_, setup_script, NAPI_AUTO_LENGTH, &script_src), napi_ok); |
| ASSERT_EQ(napi_run_script(env_, script_src, &script_res), napi_ok); |
| |
| // Resolve the deferred with an integer value from C++. |
| napi_value res_val; |
| ASSERT_EQ(napi_create_int32(env_, 98765, &res_val), napi_ok); |
| ASSERT_EQ(napi_resolve_deferred(env_, deferred, res_val), napi_ok); |
| |
| // Promise .then() callbacks are queued asynchronously as JavaScript microtasks. Drain the |
| // microtask queue so the .then() callback executes and writes to globalThis.resolvedVal. |
| RunMicrotasks(); |
| |
| // Verify that the JavaScript .then() callback ran and captured the resolved value. |
| napi_value resolved_prop; |
| ASSERT_EQ(napi_get_named_property(env_, global, "resolvedVal", &resolved_prop), napi_ok); |
| int32_t final_val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, resolved_prop, &final_val), napi_ok); |
| EXPECT_EQ(final_val, 98765); |
| } |
| |
| TEST_F(NapiV8Test, PromiseReject) { |
| // napi_create_promise creates a JavaScript Promise and a C++ napi_deferred handle. |
| napi_deferred deferred = nullptr; |
| napi_value promise = nullptr; |
| ASSERT_EQ(napi_create_promise(env_, &deferred, &promise), napi_ok); |
| ASSERT_NE(deferred, nullptr); |
| ASSERT_NE(promise, nullptr); |
| |
| // Attach a JavaScript .catch() callback to observe rejection. |
| napi_value global; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, global, "testRejectPromise", promise), napi_ok); |
| |
| napi_value script_res; |
| napi_value script_src; |
| const char* setup_script = |
| "globalThis.rejectedVal = null;\n" |
| "testRejectPromise.catch(err => { globalThis.rejectedVal = err; });"; |
| ASSERT_EQ(napi_create_string_utf8(env_, setup_script, NAPI_AUTO_LENGTH, &script_src), napi_ok); |
| ASSERT_EQ(napi_run_script(env_, script_src, &script_res), napi_ok); |
| |
| // Reject the deferred with an error string from C++. |
| napi_value err_val; |
| ASSERT_EQ(napi_create_string_utf8(env_, "something failed", NAPI_AUTO_LENGTH, &err_val), |
| napi_ok); |
| ASSERT_EQ(napi_reject_deferred(env_, deferred, err_val), napi_ok); |
| |
| // Drain the microtask queue so the .catch() callback executes. |
| RunMicrotasks(); |
| |
| // Verify that the JavaScript .catch() callback ran and captured the rejection error. |
| napi_value rejected_prop; |
| ASSERT_EQ(napi_get_named_property(env_, global, "rejectedVal", &rejected_prop), napi_ok); |
| char buf[64]; |
| size_t copied = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, rejected_prop, buf, sizeof(buf), &copied), napi_ok); |
| EXPECT_STREQ(buf, "something failed"); |
| } |
| |
| TEST_F(NapiV8Test, GetVersion) { |
| uint32_t version = 0; |
| ASSERT_EQ(napi_get_version(env_, &version), napi_ok); |
| EXPECT_EQ(version, static_cast<uint32_t>(NAPI_VERSION)); |
| } |
| |
| TEST_F(NapiV8Test, RunScript) { |
| napi_value script; |
| napi_value result; |
| ASSERT_EQ(napi_create_string_utf8(env_, "12 * 34", NAPI_AUTO_LENGTH, &script), napi_ok); |
| ASSERT_EQ(napi_run_script(env_, script, &result), napi_ok); |
| |
| int32_t val = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, result, &val), napi_ok); |
| EXPECT_EQ(val, 12 * 34); |
| |
| napi_value bad_script; |
| ASSERT_EQ(napi_create_string_utf8(env_, "syntax error {", NAPI_AUTO_LENGTH, &bad_script), |
| napi_ok); |
| EXPECT_EQ(napi_run_script(env_, bad_script, &result), napi_pending_exception); |
| bool pending = false; |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_TRUE(pending); |
| napi_value ex; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &ex), napi_ok); |
| bool is_error = false; |
| ASSERT_EQ(napi_is_error(env_, ex, &is_error), napi_ok); |
| EXPECT_TRUE(is_error); |
| } |
| |
| TEST_F(NapiV8Test, BuffersAndPromisesInvalidArgs) { |
| napi_value ab; |
| ASSERT_EQ(napi_create_arraybuffer(env_, 16, nullptr, &ab), napi_ok); |
| |
| // Null argument validations |
| EXPECT_EQ(napi_is_arraybuffer(nullptr, ab, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_arraybuffer(env_, 16, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_get_arraybuffer_info(env_, nullptr, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_detach_arraybuffer(env_, nullptr), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_is_typedarray(env_, nullptr, nullptr), napi_invalid_arg); |
| napi_value created_ta = nullptr; |
| EXPECT_EQ(napi_create_typedarray(env_, napi_int32_array, 4, ab, 0, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_create_typedarray(env_, napi_int32_array, 4, ab, 1, &created_ta), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_create_typedarray(env_, napi_int32_array, 100, ab, 0, &created_ta), |
| napi_invalid_arg); |
| EXPECT_EQ(napi_get_typedarray_info(env_, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr), |
| napi_invalid_arg); |
| |
| EXPECT_EQ(napi_create_promise(env_, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_resolve_deferred(env_, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_reject_deferred(env_, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_is_promise(env_, nullptr, nullptr), napi_invalid_arg); |
| |
| uint32_t version = 0; |
| EXPECT_EQ(napi_get_version(nullptr, &version), napi_invalid_arg); |
| EXPECT_EQ(napi_get_version(env_, nullptr), napi_invalid_arg); |
| |
| // Type mismatch checks |
| napi_value num; |
| ASSERT_EQ(napi_create_int32(env_, 1, &num), napi_ok); |
| EXPECT_EQ(napi_get_arraybuffer_info(env_, num, nullptr, nullptr), napi_arraybuffer_expected); |
| EXPECT_EQ(napi_detach_arraybuffer(env_, num), napi_arraybuffer_expected); |
| EXPECT_EQ(napi_create_typedarray(env_, napi_int32_array, 4, num, 0, &created_ta), |
| napi_arraybuffer_expected); |
| EXPECT_EQ(napi_get_typedarray_info(env_, num, nullptr, nullptr, nullptr, nullptr, nullptr), |
| napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, EscapableHandleScope) { |
| napi_escapable_handle_scope scope; |
| ASSERT_EQ(napi_open_escapable_handle_scope(env_, &scope), napi_ok); |
| napi_value val; |
| ASSERT_EQ(napi_create_int32(env_, 42, &val), napi_ok); |
| napi_value escaped; |
| ASSERT_EQ(napi_escape_handle(env_, scope, val, &escaped), napi_ok); |
| ASSERT_EQ(napi_close_escapable_handle_scope(env_, scope), napi_ok); |
| |
| int32_t result = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, escaped, &result), napi_ok); |
| EXPECT_EQ(result, 42); |
| } |
| |
| TEST_F(NapiV8Test, BigInt) { |
| napi_value bi_val; |
| int64_t in_val = -123456789012345LL; |
| ASSERT_EQ(napi_create_bigint_int64(env_, in_val, &bi_val), napi_ok); |
| |
| int64_t out_val = 0; |
| bool lossless = false; |
| ASSERT_EQ(napi_get_value_bigint_int64(env_, bi_val, &out_val, &lossless), napi_ok); |
| EXPECT_EQ(out_val, in_val); |
| EXPECT_TRUE(lossless); |
| |
| napi_value ubi_val; |
| uint64_t in_uval = 987654321098765ULL; |
| ASSERT_EQ(napi_create_bigint_uint64(env_, in_uval, &ubi_val), napi_ok); |
| |
| uint64_t out_uval = 0; |
| ASSERT_EQ(napi_get_value_bigint_uint64(env_, ubi_val, &out_uval, &lossless), napi_ok); |
| EXPECT_EQ(out_uval, in_uval); |
| EXPECT_TRUE(lossless); |
| } |
| |
| namespace { |
| void SetBoolFinalizer(napi_env, void* finalize_data, void*) { |
| *static_cast<bool*>(finalize_data) = true; |
| } |
| } // namespace |
| |
| TEST_F(NapiV8Test, AddFinalizer) { |
| bool finalized = false; |
| napi_ref ref = nullptr; |
| { |
| napi_handle_scope scope; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| ASSERT_EQ(napi_add_finalizer(env_, obj, &finalized, SetBoolFinalizer, nullptr, &ref), |
| napi_ok); |
| EXPECT_NE(ref, nullptr); |
| EXPECT_FALSE(finalized); |
| |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| RequestGC(); |
| EXPECT_TRUE(finalized); |
| |
| // The collected object leaves the reference behind for its owner to release. |
| EXPECT_EQ(napi_delete_reference(env_, ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, EscapableHandleScopeReleasesNonEscapedHandles) { |
| napi_value escaped = nullptr; |
| napi_ref kept_ref = nullptr; |
| napi_ref dropped_ref = nullptr; |
| |
| { |
| napi_escapable_handle_scope scope; |
| ASSERT_EQ(napi_open_escapable_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value kept, dropped; |
| ASSERT_EQ(napi_create_object(env_, &kept), napi_ok); |
| ASSERT_EQ(napi_create_object(env_, &dropped), napi_ok); |
| |
| // Weak, so that the references report whether each object survived rather than being the |
| // reason it did. A finalizer would not work here: the escaped object is still reachable at |
| // the end of the test, so its finalizer would not run until the environment is torn down. |
| ASSERT_EQ(napi_create_reference(env_, kept, 0, &kept_ref), napi_ok); |
| ASSERT_EQ(napi_create_reference(env_, dropped, 0, &dropped_ref), napi_ok); |
| |
| ASSERT_EQ(napi_escape_handle(env_, scope, kept, &escaped), napi_ok); |
| ASSERT_EQ(napi_close_escapable_handle_scope(env_, scope), napi_ok); |
| } |
| |
| RequestGC(); |
| |
| // The escaped handle was promoted into the enclosing scope, so its object is still rooted. |
| napi_value kept_value = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, kept_ref, &kept_value), napi_ok); |
| EXPECT_NE(kept_value, nullptr); |
| |
| // The handle that did not escape was released along with the scope. |
| napi_value dropped_value = nullptr; |
| ASSERT_EQ(napi_get_reference_value(env_, dropped_ref, &dropped_value), napi_ok); |
| EXPECT_EQ(dropped_value, nullptr); |
| |
| EXPECT_EQ(napi_delete_reference(env_, kept_ref), napi_ok); |
| EXPECT_EQ(napi_delete_reference(env_, dropped_ref), napi_ok); |
| } |
| |
| TEST_F(NapiV8Test, EscapeHandleOnlySucceedsOncePerScope) { |
| napi_escapable_handle_scope scope; |
| ASSERT_EQ(napi_open_escapable_handle_scope(env_, &scope), napi_ok); |
| |
| napi_value first, second; |
| ASSERT_EQ(napi_create_int32(env_, 1, &first), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 2, &second), napi_ok); |
| |
| napi_value escaped = nullptr; |
| ASSERT_EQ(napi_escape_handle(env_, scope, first, &escaped), napi_ok); |
| |
| // The second request must be rejected rather than overwriting the handle already promoted. |
| napi_value escaped_again = nullptr; |
| EXPECT_EQ(napi_escape_handle(env_, scope, second, &escaped_again), napi_escape_called_twice); |
| |
| ASSERT_EQ(napi_close_escapable_handle_scope(env_, scope), napi_ok); |
| |
| int32_t value = 0; |
| ASSERT_EQ(napi_get_value_int32(env_, escaped, &value), napi_ok); |
| EXPECT_EQ(value, 1); |
| } |
| |
| TEST_F(NapiV8Test, IsArray) { |
| napi_value arr, obj, num; |
| ASSERT_EQ(napi_create_array(env_, &arr), napi_ok); |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_create_int32(env_, 7, &num), napi_ok); |
| |
| bool result = false; |
| ASSERT_EQ(napi_is_array(env_, arr, &result), napi_ok); |
| EXPECT_TRUE(result); |
| ASSERT_EQ(napi_is_array(env_, obj, &result), napi_ok); |
| EXPECT_FALSE(result); |
| ASSERT_EQ(napi_is_array(env_, num, &result), napi_ok); |
| EXPECT_FALSE(result); |
| |
| EXPECT_EQ(napi_is_array(nullptr, arr, &result), napi_invalid_arg); |
| EXPECT_EQ(napi_is_array(env_, nullptr, &result), napi_invalid_arg); |
| EXPECT_EQ(napi_is_array(env_, arr, nullptr), napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, InstanceOf) { |
| napi_value src, ctor_a, ctor_b; |
| ASSERT_EQ(napi_create_string_utf8(env_, "(class A {})", NAPI_AUTO_LENGTH, &src), napi_ok); |
| ASSERT_EQ(napi_run_script(env_, src, &ctor_a), napi_ok); |
| ASSERT_EQ(napi_create_string_utf8(env_, "(class B {})", NAPI_AUTO_LENGTH, &src), napi_ok); |
| ASSERT_EQ(napi_run_script(env_, src, &ctor_b), napi_ok); |
| |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, ctor_a, 0, nullptr, &inst), napi_ok); |
| |
| bool result = false; |
| ASSERT_EQ(napi_instanceof(env_, inst, ctor_a, &result), napi_ok); |
| EXPECT_TRUE(result); |
| ASSERT_EQ(napi_instanceof(env_, inst, ctor_b, &result), napi_ok); |
| EXPECT_FALSE(result); |
| |
| napi_value num; |
| ASSERT_EQ(napi_create_int32(env_, 1, &num), napi_ok); |
| EXPECT_EQ(napi_instanceof(env_, inst, num, &result), napi_object_expected); |
| |
| // A non-callable constructor makes JavaScript throw, which is surfaced as a pending exception. |
| napi_value plain; |
| ASSERT_EQ(napi_create_object(env_, &plain), napi_ok); |
| EXPECT_EQ(napi_instanceof(env_, inst, plain, &result), napi_pending_exception); |
| bool pending = false; |
| ASSERT_EQ(napi_is_exception_pending(env_, &pending), napi_ok); |
| EXPECT_TRUE(pending); |
| napi_value exception; |
| ASSERT_EQ(napi_get_and_clear_last_exception(env_, &exception), napi_ok); |
| |
| EXPECT_EQ(napi_instanceof(nullptr, inst, ctor_a, &result), napi_invalid_arg); |
| EXPECT_EQ(napi_instanceof(env_, nullptr, ctor_a, &result), napi_invalid_arg); |
| EXPECT_EQ(napi_instanceof(env_, inst, nullptr, &result), napi_invalid_arg); |
| EXPECT_EQ(napi_instanceof(env_, inst, ctor_a, nullptr), napi_invalid_arg); |
| } |
| |
| namespace { |
| struct NewTargetRecord { |
| napi_status status = napi_generic_failure; |
| bool is_undefined = false; |
| bool matches_callee = false; |
| napi_value callee = nullptr; |
| }; |
| |
| napi_value RecordNewTarget(napi_env env, napi_callback_info info) { |
| void* data = nullptr; |
| napi_value this_arg = nullptr; |
| napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, &data); |
| auto* record = static_cast<NewTargetRecord*>(data); |
| |
| napi_value new_target = nullptr; |
| record->status = napi_get_new_target(env, info, &new_target); |
| |
| napi_valuetype type = napi_object; |
| napi_typeof(env, new_target, &type); |
| record->is_undefined = type == napi_undefined; |
| napi_strict_equals(env, new_target, record->callee, &record->matches_callee); |
| return this_arg; |
| } |
| } // namespace |
| |
| TEST_F(NapiV8Test, GetNewTarget) { |
| NewTargetRecord record; |
| napi_value fn; |
| ASSERT_EQ(napi_create_function(env_, "Target", NAPI_AUTO_LENGTH, RecordNewTarget, &record, &fn), |
| napi_ok); |
| record.callee = fn; |
| |
| // Called as a plain function, new.target is undefined. |
| napi_value global, call_result; |
| ASSERT_EQ(napi_get_global(env_, &global), napi_ok); |
| ASSERT_EQ(napi_call_function(env_, global, fn, 0, nullptr, &call_result), napi_ok); |
| EXPECT_EQ(record.status, napi_ok); |
| EXPECT_TRUE(record.is_undefined); |
| EXPECT_FALSE(record.matches_callee); |
| |
| // Called as a constructor, new.target is the function being constructed. |
| napi_value inst; |
| ASSERT_EQ(napi_new_instance(env_, fn, 0, nullptr, &inst), napi_ok); |
| EXPECT_EQ(record.status, napi_ok); |
| EXPECT_FALSE(record.is_undefined); |
| EXPECT_TRUE(record.matches_callee); |
| |
| napi_value target = nullptr; |
| EXPECT_EQ(napi_get_new_target(env_, nullptr, &target), napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, External) { |
| TestNativeData data{4321}; |
| napi_value ext; |
| ASSERT_EQ(napi_create_external(env_, &data, nullptr, nullptr, &ext), napi_ok); |
| |
| napi_valuetype type = napi_undefined; |
| ASSERT_EQ(napi_typeof(env_, ext, &type), napi_ok); |
| EXPECT_EQ(type, napi_external); |
| |
| void* retrieved = nullptr; |
| ASSERT_EQ(napi_get_value_external(env_, ext, &retrieved), napi_ok); |
| EXPECT_EQ(retrieved, &data); |
| EXPECT_EQ(static_cast<TestNativeData*>(retrieved)->value, 4321); |
| |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| EXPECT_EQ(napi_get_value_external(env_, obj, &retrieved), napi_invalid_arg); |
| |
| EXPECT_EQ(napi_create_external(nullptr, &data, nullptr, nullptr, &ext), napi_invalid_arg); |
| EXPECT_EQ(napi_create_external(env_, &data, nullptr, nullptr, nullptr), napi_invalid_arg); |
| EXPECT_EQ(napi_get_value_external(nullptr, ext, &retrieved), napi_invalid_arg); |
| EXPECT_EQ(napi_get_value_external(env_, nullptr, &retrieved), napi_invalid_arg); |
| EXPECT_EQ(napi_get_value_external(env_, ext, nullptr), napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, UnwrapObjectThatWasNeverWrapped) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| void* unwrapped = nullptr; |
| EXPECT_EQ(napi_unwrap(env_, obj, &unwrapped), napi_invalid_arg); |
| EXPECT_EQ(napi_remove_wrap(env_, obj, &unwrapped), napi_invalid_arg); |
| } |
| |
| TEST_F(NapiV8Test, WrapIsInvisibleToJavaScript) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| TestNativeData data{11}; |
| ASSERT_EQ(napi_wrap(env_, obj, &data, nullptr, nullptr, nullptr), napi_ok); |
| |
| // Wrapping must not add anything a script can see. |
| napi_value names; |
| ASSERT_EQ(napi_get_property_names(env_, obj, &names), napi_ok); |
| uint32_t length = 1; |
| ASSERT_EQ(napi_get_array_length(env_, names, &length), napi_ok); |
| EXPECT_EQ(length, 0u); |
| |
| bool has = true; |
| ASSERT_EQ(napi_has_named_property(env_, obj, "node:napi:wrapper", &has), napi_ok); |
| EXPECT_FALSE(has); |
| } |
| |
| TEST_F(NapiV8Test, UnwrapIgnoresLookalikeProperty) { |
| napi_value obj; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| |
| // An ordinary property whose name matches the private key's description, holding a value of |
| // the same shape a wrapper uses, must not be mistaken for one. |
| TestNativeData decoy{99}; |
| napi_value ext; |
| ASSERT_EQ(napi_create_external(env_, &decoy, nullptr, nullptr, &ext), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env_, obj, "node:napi:wrapper", ext), napi_ok); |
| |
| void* unwrapped = nullptr; |
| EXPECT_EQ(napi_unwrap(env_, obj, &unwrapped), napi_invalid_arg); |
| EXPECT_EQ(unwrapped, nullptr); |
| |
| // The decoy also must not stop the object from being wrapped for real. |
| TestNativeData real{42}; |
| ASSERT_EQ(napi_wrap(env_, obj, &real, nullptr, nullptr, nullptr), napi_ok); |
| ASSERT_EQ(napi_unwrap(env_, obj, &unwrapped), napi_ok); |
| EXPECT_EQ(unwrapped, &real); |
| } |
| |
| void AllocateJsObjectInFinalizer(napi_env env, void* finalize_data, void*) { |
| auto* ran = static_cast<bool*>(finalize_data); |
| napi_handle_scope scope = nullptr; |
| ASSERT_EQ(napi_open_handle_scope(env, &scope), napi_ok); |
| |
| napi_value obj = nullptr; |
| ASSERT_EQ(napi_create_object(env, &obj), napi_ok); |
| napi_value marker = nullptr; |
| ASSERT_EQ(napi_create_string_utf8(env, "allocated-in-finalizer", NAPI_AUTO_LENGTH, &marker), |
| napi_ok); |
| ASSERT_EQ(napi_set_named_property(env, obj, "status", marker), napi_ok); |
| |
| napi_value global = nullptr; |
| ASSERT_EQ(napi_get_global(env, &global), napi_ok); |
| ASSERT_EQ(napi_set_named_property(env, global, "_fromFinalizer", obj), napi_ok); |
| |
| ASSERT_EQ(napi_close_handle_scope(env, scope), napi_ok); |
| *ran = true; |
| } |
| |
| // Node-API `napi_finalize` callbacks receive a `napi_env` and are allowed to allocate JavaScript |
| // objects and call into V8, which requires running them outside V8's first-pass GC pause. |
| TEST_F(NapiV8Test, FinalizerCanAllocateJavaScriptObjects) { |
| bool finalized = false; |
| { |
| napi_handle_scope scope = nullptr; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| napi_value obj = nullptr; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_add_finalizer(env_, obj, &finalized, AllocateJsObjectInFinalizer, nullptr, |
| nullptr), |
| napi_ok); |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| RequestGC(); |
| EXPECT_TRUE(finalized); |
| |
| napi_value script = nullptr; |
| ASSERT_EQ(napi_create_string_utf8(env_, "globalThis._fromFinalizer.status", NAPI_AUTO_LENGTH, |
| &script), |
| napi_ok); |
| napi_value result = nullptr; |
| ASSERT_EQ(napi_run_script(env_, script, &result), napi_ok); |
| char buf[64] = {}; |
| size_t len = 0; |
| ASSERT_EQ(napi_get_value_string_utf8(env_, result, buf, sizeof(buf), &len), napi_ok); |
| EXPECT_STREQ(buf, "allocated-in-finalizer"); |
| } |
| |
| void RecordFinalizedFlag(napi_env, void* finalize_data, void*) { |
| *static_cast<bool*>(finalize_data) = true; |
| } |
| |
| // GC schedules `napi_finalize` callbacks onto `globalThis.setImmediate` rather than running them |
| // during `CollectGarbage()`. |
| TEST_F(NapiV8Test, FinalizerIsDeferredThroughSetImmediate) { |
| bool finalized = false; |
| { |
| napi_handle_scope scope = nullptr; |
| ASSERT_EQ(napi_open_handle_scope(env_, &scope), napi_ok); |
| napi_value obj = nullptr; |
| ASSERT_EQ(napi_create_object(env_, &obj), napi_ok); |
| ASSERT_EQ(napi_add_finalizer(env_, obj, &finalized, RecordFinalizedFlag, nullptr, nullptr), |
| napi_ok); |
| ASSERT_EQ(napi_close_handle_scope(env_, scope), napi_ok); |
| } |
| |
| isolate_->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection); |
| EXPECT_FALSE(finalized); |
| |
| DrainImmediates(); |
| EXPECT_TRUE(finalized); |
| } |
| |
| } // namespace |