blob: 61db6c83f89eef8934a2d2dcc2e110697bda3c0b [file] [edit]
// 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, &not_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