Files
bun.sh/test/napi/napi-app/standalone_tests.cpp
Jarred Sumner 359f04d81f Improve NAPI property and element handling (#24358)
### What does this PR do?

Refactored NAPI property and element access to use inline methods and
improved error handling. Added comprehensive tests for default value
behavior and numeric string key operations in NAPI, ensuring correct
handling of missing properties, integer keys, and property deletion.
Updated TypeScript tests to cover new scenarios.

### How did you verify your code works?

Tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-11-04 03:21:07 -08:00

1863 lines
61 KiB
C++

#include "standalone_tests.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <iostream>
#include <string>
#include "utils.h"
namespace napitests {
// https://github.com/oven-sh/bun/issues/7685
static napi_value test_issue_7685(const Napi::CallbackInfo &info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
// info[0] is a function to run the GC
NODE_API_ASSERT(env, info[1].IsNumber());
NODE_API_ASSERT(env, info[2].IsNumber());
NODE_API_ASSERT(env, info[3].IsNumber());
NODE_API_ASSERT(env, info[4].IsNumber());
NODE_API_ASSERT(env, info[5].IsNumber());
NODE_API_ASSERT(env, info[6].IsNumber());
NODE_API_ASSERT(env, info[7].IsNumber());
NODE_API_ASSERT(env, info[8].IsNumber());
return ok(env);
}
static napi_threadsafe_function tsfn_11949 = nullptr;
static void test_issue_11949_callback(napi_env env, napi_value js_callback,
void *opaque_context, void *opaque_data) {
int *context = reinterpret_cast<int *>(opaque_context);
int *data = reinterpret_cast<int *>(opaque_data);
printf("data = %d, context = %d\n", *data, *context);
delete context;
delete data;
napi_unref_threadsafe_function(env, tsfn_11949);
tsfn_11949 = nullptr;
}
// https://github.com/oven-sh/bun/issues/11949
static napi_value test_issue_11949(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
napi_value name = Napi::String::New(env, "TSFN");
int *context = new int(42);
int *data = new int(1234);
NODE_API_CALL(env,
napi_create_threadsafe_function(
env, /* JavaScript function */ nullptr,
/* async resource */ nullptr, name,
/* max queue size (unlimited) */ 0,
/* initial thread count */ 1, /* finalize data */ nullptr,
/* finalize callback */ nullptr, context,
&test_issue_11949_callback, &tsfn_11949));
NODE_API_CALL(env, napi_call_threadsafe_function(tsfn_11949, data,
napi_tsfn_nonblocking));
return env.Undefined();
}
static void noop_callback(napi_env env, napi_value js_callback, void *context,
void *data) {}
static napi_value test_napi_threadsafe_function_does_not_hang_after_finalize(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
napi_value resource_name = Napi::String::New(env, "simple");
napi_threadsafe_function cb;
NODE_API_CALL(env,
napi_create_threadsafe_function(
env, /* JavaScript function */ nullptr,
/* async resource */ nullptr, resource_name,
/* max queue size (unlimited) */ 0,
/* initial thread count */ 1, /* finalize data */ nullptr,
/* finalize callback */ nullptr, /* context */ nullptr,
&noop_callback, &cb));
NODE_API_CALL(env, napi_release_threadsafe_function(cb, napi_tsfn_release));
printf("success!\n");
return env.Undefined();
}
static napi_value
test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// info[0] is a function to run the GC
napi_value string_js = info[1];
// get how many chars we need to copy
size_t len = info[2].As<Napi::Number>().Uint32Value();
if (len == 424242) {
len = NAPI_AUTO_LENGTH;
} else {
NODE_API_ASSERT(env, len <= 29);
}
size_t copied;
const size_t BUF_SIZE = 30;
char buf[BUF_SIZE];
memset(buf, '*', BUF_SIZE);
buf[BUF_SIZE - 1] = '\0';
NODE_API_CALL(env,
napi_get_value_string_utf8(env, string_js, buf, len, &copied));
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
std::cout << "Chars to copy: " << len << std::endl;
std::cout << "Copied chars: " << copied << std::endl;
std::cout << "Buffer: ";
for (size_t i = 0; i < BUF_SIZE; i++) {
std::cout << (int)buf[i] << ", ";
}
std::cout << std::endl;
std::cout << "Value str: " << buf << std::endl;
return ok(env);
}
static napi_value
test_napi_handle_scope_string(const Napi::CallbackInfo &info) {
// this is mostly a copy of test_handle_scope_gc from
// test/v8/v8-module/main.cpp -- see comments there for explanation
Napi::Env env = info.Env();
constexpr size_t num_small_strings = 10000;
auto *small_strings = new napi_value[num_small_strings];
for (size_t i = 0; i < num_small_strings; i++) {
std::string cpp_str = std::to_string(i);
NODE_API_CALL(env,
napi_create_string_utf8(env, cpp_str.c_str(), cpp_str.size(),
&small_strings[i]));
}
run_gc(info);
for (size_t j = 0; j < num_small_strings; j++) {
char buf[16];
size_t result;
NODE_API_CALL(env, napi_get_value_string_utf8(env, small_strings[j], buf,
sizeof buf, &result));
NODE_API_ASSERT(env, atoi(buf) == (int)j);
}
delete[] small_strings;
return ok(env);
}
static napi_value
test_napi_handle_scope_bigint(const Napi::CallbackInfo &info) {
// this is mostly a copy of test_handle_scope_gc from
// test/v8/v8-module/main.cpp -- see comments there for explanation
Napi::Env env = info.Env();
constexpr size_t num_small_ints = 10000;
constexpr size_t small_int_size = 100;
auto *small_ints = new napi_value[num_small_ints];
for (size_t i = 0, small_int_index = 1; i < num_small_ints;
i++, small_int_index++) {
uint64_t words[small_int_size];
for (size_t j = 0; j < small_int_size; j++) {
words[j] = small_int_index;
}
NODE_API_CALL(env, napi_create_bigint_words(env, 0, small_int_size, words,
&small_ints[i]));
}
run_gc(info);
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
for (size_t j = 0; j < num_small_ints; j++) {
std::array<uint64_t, small_int_size> words;
int sign;
size_t word_count = words.size();
NODE_API_CALL(env, napi_get_value_bigint_words(env, small_ints[j], &sign,
&word_count, words.data()));
printf("%d, %zu\n", sign, word_count);
NODE_API_ASSERT(env, sign == 0 && word_count == words.size());
NODE_API_ASSERT(env,
std::all_of(words.begin(), words.end(),
[j](const uint64_t &w) { return w == j + 1; }));
}
delete[] small_ints;
return ok(env);
}
static napi_value test_napi_delete_property(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// info[0] is a function to run the GC
napi_value object = info[1];
napi_valuetype type = get_typeof(env, object);
NODE_API_ASSERT(env, type == napi_object);
napi_value key = Napi::String::New(env, "foo");
napi_value non_configurable_key = Napi::String::New(env, "bar");
napi_value val;
NODE_API_CALL(env, napi_create_int32(env, 42, &val));
bool delete_result;
NODE_API_CALL(env, napi_delete_property(env, object, non_configurable_key,
&delete_result));
NODE_API_ASSERT(env, delete_result == false);
NODE_API_CALL(env, napi_delete_property(env, object, key, &delete_result));
NODE_API_ASSERT(env, delete_result == true);
bool has_property;
NODE_API_CALL(env, napi_has_property(env, object, key, &has_property));
NODE_API_ASSERT(env, has_property == false);
return ok(env);
}
// Returns false if any napi function failed
static bool store_escaped_handle(napi_env env, napi_value *out,
const char *str) {
// Allocate these values on the heap so they cannot be seen by stack scanning
// after this function returns. An earlier version tried putting them on the
// stack and using volatile stores to set them to nullptr, but that wasn't
// effective when the NAPI module was built in release mode as extra copies of
// the pointers would still be left in uninitialized stack memory.
napi_escapable_handle_scope *ehs = new napi_escapable_handle_scope;
napi_value *s = new napi_value;
napi_value *escaped = new napi_value;
NODE_API_CALL_CUSTOM_RETURN(env, false,
napi_open_escapable_handle_scope(env, ehs));
NODE_API_CALL_CUSTOM_RETURN(
env, false, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, s));
NODE_API_CALL_CUSTOM_RETURN(env, false,
napi_escape_handle(env, *ehs, *s, escaped));
// can't call a second time
NODE_API_ASSERT_CUSTOM_RETURN(env, false,
napi_escape_handle(env, *ehs, *s, escaped) ==
napi_escape_called_twice);
NODE_API_CALL_CUSTOM_RETURN(env, false,
napi_close_escapable_handle_scope(env, *ehs));
*out = *escaped;
delete escaped;
delete s;
delete ehs;
return true;
}
static napi_value
test_napi_escapable_handle_scope(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// allocate space for a napi_value on the heap
// use store_escaped_handle to put the value into it
// trigger GC
// the napi_value should still be valid even though it can't be found on the
// stack, because it escaped into the current handle scope
constexpr const char *str = "this is a long string meow meow meow";
napi_value *hidden = new napi_value;
NODE_API_ASSERT(env, store_escaped_handle(env, hidden, str));
run_gc(info);
char buf[64];
size_t len;
NODE_API_CALL(
env, napi_get_value_string_utf8(env, *hidden, buf, sizeof(buf), &len));
NODE_API_ASSERT(env, len == strlen(str));
NODE_API_ASSERT(env, strcmp(buf, str) == 0);
delete hidden;
return ok(env);
}
static napi_value
test_napi_handle_scope_nesting(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
constexpr const char *str = "this is a long string meow meow meow";
// Create an outer handle scope, hidden on the heap (the one created in
// NAPIFunction::call is still on the stack
napi_handle_scope *outer_hs = new napi_handle_scope;
NODE_API_CALL(env, napi_open_handle_scope(env, outer_hs));
// Make a handle in the outer scope, on the heap so stack scanning can't see
// it
napi_value *outer_scope_handle = new napi_value;
NODE_API_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH,
outer_scope_handle));
// Make a new handle scope on the heap so that the outer handle scope isn't
// active anymore
napi_handle_scope *inner_hs = new napi_handle_scope;
NODE_API_CALL(env, napi_open_handle_scope(env, inner_hs));
// Force GC
run_gc(info);
// Try to read our first handle. Did the outer handle scope get
// collected now that it's not on the global object? The inner handle scope
// should be keeping it alive even though it's not on the stack.
char buf[64];
size_t len;
NODE_API_CALL(env, napi_get_value_string_utf8(env, *outer_scope_handle, buf,
sizeof(buf), &len));
NODE_API_ASSERT(env, len == strlen(str));
NODE_API_ASSERT(env, strcmp(buf, str) == 0);
// Clean up
NODE_API_CALL(env, napi_close_handle_scope(env, *inner_hs));
delete inner_hs;
NODE_API_CALL(env, napi_close_handle_scope(env, *outer_hs));
delete outer_hs;
delete outer_scope_handle;
return ok(env);
}
// call this with a bunch (>10) of string arguments representing increasing
// decimal numbers. ensures that the runtime does not let these arguments be
// freed.
//
// test_napi_handle_scope_many_args(() => gc(), '1', '2', '3', ...)
static napi_value
test_napi_handle_scope_many_args(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
run_gc(info);
// now if bun is broken a bunch of our args are dead, because node-addon-api
// uses a heap array for >6 args
for (size_t i = 1; i < info.Length(); i++) {
Napi::String s = info[i].As<Napi::String>();
NODE_API_ASSERT(env, s.Utf8Value() == std::to_string(i));
}
return env.Undefined();
}
static napi_value test_napi_ref(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
napi_value object;
NODE_API_CALL(env, napi_create_object(env, &object));
napi_ref ref;
NODE_API_CALL(env, napi_create_reference(env, object, 0, &ref));
napi_value from_ref;
NODE_API_CALL(env, napi_get_reference_value(env, ref, &from_ref));
NODE_API_ASSERT(env, from_ref != nullptr);
napi_valuetype typeof_result = get_typeof(env, from_ref);
NODE_API_ASSERT(env, typeof_result == napi_object);
return ok(env);
}
static napi_value test_napi_run_script(const Napi::CallbackInfo &info) {
napi_value ret = nullptr;
// info[0] is the GC callback
(void)napi_run_script(info.Env(), info[1], &ret);
return ret;
}
static napi_value test_napi_throw_with_nullptr(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
const napi_status status = napi_throw(env, nullptr);
printf("napi_throw -> %d\n", status);
bool is_exception_pending;
NODE_API_CALL(env, napi_is_exception_pending(env, &is_exception_pending));
printf("napi_is_exception_pending -> %s\n",
is_exception_pending ? "true" : "false");
return ok(env);
}
// Call Node-API functions in ways that result in different error handling
// (erroneous call, valid call, or valid call while an exception is pending) and
// log information from napi_get_last_error_info
static napi_value test_extended_error_messages(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
const napi_extended_error_info *error;
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
// this function is implemented in C++
// error because the result pointer is null
printf("erroneous napi_create_double returned code %d\n",
napi_create_double(env, 1.0, nullptr));
NODE_API_CALL(env, napi_get_last_error_info(env, &error));
printf("erroneous napi_create_double info: code = %d, message = %s\n",
error->error_code, error->error_message);
// this function should succeed and the success should overwrite the error
// from the last call
napi_value js_number;
printf("successful napi_create_double returned code %d\n",
napi_create_double(env, 5.0, &js_number));
NODE_API_CALL(env, napi_get_last_error_info(env, &error));
printf("successful napi_create_double info: code = %d, message = %s\n",
error->error_code,
error->error_message ? error->error_message : "(null)");
// this function is implemented in zig
// error because the value is not an array
unsigned int len;
printf("erroneous napi_get_array_length returned code %d\n",
napi_get_array_length(env, js_number, &len));
NODE_API_CALL(env, napi_get_last_error_info(env, &error));
printf("erroneous napi_get_array_length info: code = %d, message = %s\n",
error->error_code, error->error_message);
// throw an exception
NODE_API_CALL(env, napi_throw_type_error(env, nullptr, "oops!"));
// nothing is wrong with this call by itself, but it should return
// napi_pending_exception without doing anything because an exception is
// pending
napi_value coerced_string;
printf("napi_coerce_to_string with pending exception returned code %d\n",
napi_coerce_to_string(env, js_number, &coerced_string));
NODE_API_CALL(env, napi_get_last_error_info(env, &error));
printf(
"napi_coerce_to_string with pending exception info: code = %d, message = "
"%s\n",
error->error_code, error->error_message);
// clear the exception
napi_value exception;
NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &exception));
return ok(env);
}
static napi_value bigint_to_i64(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
// start at 1 is intentional, since argument 0 is the callback to run GC
// passed to every function
// perform test on all arguments
for (size_t i = 1; i < info.Length(); i++) {
napi_value bigint = info[i];
napi_valuetype type;
NODE_API_CALL(env, napi_typeof(env, bigint, &type));
int64_t result = 0;
bool lossless = false;
if (type != napi_bigint) {
printf("napi_get_value_bigint_int64 return for non-bigint: %d\n",
napi_get_value_bigint_int64(env, bigint, &result, &lossless));
} else {
NODE_API_CALL(
env, napi_get_value_bigint_int64(env, bigint, &result, &lossless));
printf("napi_get_value_bigint_int64 result: %" PRId64 "\n", result);
printf("lossless: %s\n", lossless ? "true" : "false");
}
}
return ok(env);
}
static napi_value bigint_to_u64(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
// start at 1 is intentional, since argument 0 is the callback to run GC
// passed to every function
// perform test on all arguments
for (size_t i = 1; i < info.Length(); i++) {
napi_value bigint = info[i];
napi_valuetype type;
NODE_API_CALL(env, napi_typeof(env, bigint, &type));
uint64_t result;
bool lossless;
if (type != napi_bigint) {
printf("napi_get_value_bigint_uint64 return for non-bigint: %d\n",
napi_get_value_bigint_uint64(env, bigint, &result, &lossless));
} else {
NODE_API_CALL(
env, napi_get_value_bigint_uint64(env, bigint, &result, &lossless));
printf("napi_get_value_bigint_uint64 result: %" PRIu64 "\n", result);
printf("lossless: %s\n", lossless ? "true" : "false");
}
}
return ok(env);
}
static napi_value bigint_to_64_null(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
napi_value bigint;
NODE_API_CALL(env, napi_create_bigint_int64(env, 5, &bigint));
int64_t result_signed;
uint64_t result_unsigned;
bool lossless;
printf("status (int64, null result) = %d\n",
napi_get_value_bigint_int64(env, bigint, nullptr, &lossless));
printf("status (int64, null lossless) = %d\n",
napi_get_value_bigint_int64(env, bigint, &result_signed, nullptr));
printf("status (uint64, null result) = %d\n",
napi_get_value_bigint_uint64(env, bigint, nullptr, &lossless));
printf("status (uint64, null lossless) = %d\n",
napi_get_value_bigint_uint64(env, bigint, &result_unsigned, nullptr));
return ok(env);
}
static napi_value test_is_buffer(const Napi::CallbackInfo &info) {
bool result;
napi_env env = info.Env();
NODE_API_CALL(info.Env(), napi_is_buffer(env, info[1], &result));
printf("napi_is_buffer -> %s\n", result ? "true" : "false");
return ok(env);
}
static napi_value test_is_typedarray(const Napi::CallbackInfo &info) {
bool result;
napi_env env = info.Env();
NODE_API_CALL(info.Env(), napi_is_typedarray(env, info[1], &result));
printf("napi_is_typedarray -> %s\n", result ? "true" : "false");
return ok(env);
}
static napi_value test_napi_get_default_values(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
napi_value obj;
NODE_API_CALL(env, napi_create_object(env, &obj));
// Test 1: Get property that doesn't exist (should return undefined)
napi_value unknown_key;
NODE_API_CALL(env, napi_create_string_utf8(env, "nonexistent",
NAPI_AUTO_LENGTH, &unknown_key));
napi_value result;
napi_status get_status = napi_get_property(env, obj, unknown_key, &result);
if (get_status == napi_ok) {
napi_valuetype result_type;
napi_status type_status = napi_typeof(env, result, &result_type);
if (type_status == napi_ok && result_type == napi_undefined) {
printf("PASS: napi_get_property for unknown key returned undefined\n");
} else {
printf("FAIL: napi_get_property for unknown key returned type %d "
"(expected napi_undefined)\n",
result_type);
}
} else {
printf("FAIL: napi_get_property for unknown key failed with status %d\n",
get_status);
}
// Test 2: Get element at index that doesn't exist on array
napi_value array;
NODE_API_CALL(env, napi_create_array_with_length(env, 2, &array));
napi_value element_result;
napi_status element_status = napi_get_element(env, array, 5, &element_result);
if (element_status == napi_ok) {
napi_valuetype element_type;
napi_status element_type_status =
napi_typeof(env, element_result, &element_type);
if (element_type_status == napi_ok && element_type == napi_undefined) {
printf("PASS: napi_get_element for out-of-bounds index returned "
"undefined\n");
} else {
printf("FAIL: napi_get_element for out-of-bounds index returned type %d "
"(expected napi_undefined)\n",
element_type);
}
} else {
printf("FAIL: napi_get_element for out-of-bounds index failed with status "
"%d\n",
element_status);
}
// Test 3: Get named property that doesn't exist
napi_value named_result;
napi_status named_status =
napi_get_named_property(env, obj, "missing_prop", &named_result);
if (named_status == napi_ok) {
napi_valuetype named_type;
napi_status named_type_status = napi_typeof(env, named_result, &named_type);
if (named_type_status == napi_ok && named_type == napi_undefined) {
printf("PASS: napi_get_named_property for unknown property returned "
"undefined\n");
} else {
printf("FAIL: napi_get_named_property for unknown property returned type "
"%d (expected napi_undefined)\n",
named_type);
}
} else {
printf("FAIL: napi_get_named_property for unknown property failed with "
"status %d\n",
named_status);
}
// Test 4: Set a property and verify we can get it back
napi_value test_key;
napi_value test_value;
NODE_API_CALL(env, napi_create_string_utf8(env, "test_key", NAPI_AUTO_LENGTH,
&test_key));
NODE_API_CALL(env, napi_create_int32(env, 42, &test_value));
NODE_API_CALL(env, napi_set_property(env, obj, test_key, test_value));
napi_value retrieved_value;
NODE_API_CALL(env, napi_get_property(env, obj, test_key, &retrieved_value));
int32_t retrieved_int;
napi_status int_status =
napi_get_value_int32(env, retrieved_value, &retrieved_int);
if (int_status == napi_ok && retrieved_int == 42) {
printf("PASS: napi_get_property correctly retrieved set value: %d\n",
retrieved_int);
} else {
printf("FAIL: napi_get_property did not retrieve correct value (got %d, "
"expected 42)\n",
retrieved_int);
}
// Test 5: Use integer as property key (should be converted to string)
napi_value int_key;
napi_value int_key_value;
NODE_API_CALL(env, napi_create_int32(env, 123, &int_key));
NODE_API_CALL(env, napi_create_string_utf8(env, "integer_key_value",
NAPI_AUTO_LENGTH, &int_key_value));
// Set property using integer key
napi_status int_key_set_status =
napi_set_property(env, obj, int_key, int_key_value);
if (int_key_set_status == napi_ok) {
printf("PASS: napi_set_property with integer key succeeded\n");
// Try to get it back using the same integer key
napi_value int_key_result;
napi_status int_key_get_status =
napi_get_property(env, obj, int_key, &int_key_result);
if (int_key_get_status == napi_ok) {
// Check if we got back a string
napi_valuetype int_key_result_type;
napi_status int_key_type_status =
napi_typeof(env, int_key_result, &int_key_result_type);
if (int_key_type_status == napi_ok &&
int_key_result_type == napi_string) {
char buffer[256];
size_t copied;
napi_status str_status = napi_get_value_string_utf8(
env, int_key_result, buffer, sizeof(buffer), &copied);
if (str_status == napi_ok && strcmp(buffer, "integer_key_value") == 0) {
printf("PASS: napi_get_property with integer key retrieved correct "
"value: %s\n",
buffer);
} else {
printf("FAIL: napi_get_property with integer key retrieved wrong "
"value: %s\n",
buffer);
}
} else {
printf("FAIL: napi_get_property with integer key returned type %d "
"(expected string)\n",
int_key_result_type);
}
} else {
printf("FAIL: napi_get_property with integer key failed with status %d\n",
int_key_get_status);
}
// Also try to get it using string "123"
napi_value string_123_key;
NODE_API_CALL(env, napi_create_string_utf8(env, "123", NAPI_AUTO_LENGTH,
&string_123_key));
napi_value string_key_result;
napi_status string_key_get_status =
napi_get_property(env, obj, string_123_key, &string_key_result);
if (string_key_get_status == napi_ok) {
napi_valuetype string_key_result_type;
napi_status string_key_type_status =
napi_typeof(env, string_key_result, &string_key_result_type);
if (string_key_type_status == napi_ok &&
string_key_result_type == napi_string) {
char buffer2[256];
size_t copied2;
napi_status str_status2 = napi_get_value_string_utf8(
env, string_key_result, buffer2, sizeof(buffer2), &copied2);
if (str_status2 == napi_ok &&
strcmp(buffer2, "integer_key_value") == 0) {
printf("PASS: napi_get_property with string '123' key also retrieved "
"correct value: %s\n",
buffer2);
} else {
printf("FAIL: napi_get_property with string '123' key retrieved "
"wrong value: %s\n",
buffer2);
}
} else {
printf("FAIL: napi_get_property with string '123' key returned type %d "
"(expected string)\n",
string_key_result_type);
}
} else {
printf("FAIL: napi_get_property with string '123' key failed with status "
"%d\n",
string_key_get_status);
}
} else {
printf("FAIL: napi_set_property with integer key failed with status %d\n",
int_key_set_status);
}
return ok(env);
}
static napi_value
test_napi_numeric_string_keys(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
#ifndef _WIN32
BlockingStdoutScope stdout_scope;
#endif
napi_value obj;
NODE_API_CALL(env, napi_create_object(env, &obj));
// Test setting property with numeric string key "0"
napi_value value_123;
NODE_API_CALL(env, napi_create_int32(env, 123, &value_123));
napi_status set_status = napi_set_named_property(env, obj, "0", value_123);
if (set_status == napi_ok) {
printf("PASS: napi_set_named_property with key '0' succeeded\n");
} else {
printf("FAIL: napi_set_named_property with key '0' failed: %d\n",
set_status);
}
// Test has property with numeric string key "0"
bool has_prop;
napi_status has_status = napi_has_named_property(env, obj, "0", &has_prop);
if (has_status == napi_ok && has_prop) {
printf("PASS: napi_has_named_property with key '0' returned true\n");
} else {
printf("FAIL: napi_has_named_property with key '0' failed or returned "
"false: status=%d, has=%s\n",
has_status, has_prop ? "true" : "false");
}
// Test getting property with numeric string key "0"
napi_value retrieved_value;
napi_status get_status =
napi_get_named_property(env, obj, "0", &retrieved_value);
if (get_status == napi_ok) {
int32_t retrieved_int;
napi_status int_status =
napi_get_value_int32(env, retrieved_value, &retrieved_int);
if (int_status == napi_ok && retrieved_int == 123) {
printf("PASS: napi_get_named_property with key '0' returned correct "
"value: %d\n",
retrieved_int);
} else {
printf("FAIL: napi_get_named_property with key '0' returned wrong value: "
"status=%d, value=%d\n",
int_status, retrieved_int);
}
} else {
printf("FAIL: napi_get_named_property with key '0' failed: %d\n",
get_status);
}
// Test with another numeric string key "1"
napi_value value_456;
NODE_API_CALL(env, napi_create_int32(env, 456, &value_456));
set_status = napi_set_named_property(env, obj, "1", value_456);
if (set_status == napi_ok) {
printf("PASS: napi_set_named_property with key '1' succeeded\n");
} else {
printf("FAIL: napi_set_named_property with key '1' failed: %d\n",
set_status);
}
has_status = napi_has_named_property(env, obj, "1", &has_prop);
if (has_status == napi_ok && has_prop) {
printf("PASS: napi_has_named_property with key '1' returned true\n");
} else {
printf("FAIL: napi_has_named_property with key '1' failed or returned "
"false: status=%d, has=%s\n",
has_status, has_prop ? "true" : "false");
}
get_status = napi_get_named_property(env, obj, "1", &retrieved_value);
if (get_status == napi_ok) {
int32_t retrieved_int;
napi_status int_status =
napi_get_value_int32(env, retrieved_value, &retrieved_int);
if (int_status == napi_ok && retrieved_int == 456) {
printf("PASS: napi_get_named_property with key '1' returned correct "
"value: %d\n",
retrieved_int);
} else {
printf("FAIL: napi_get_named_property with key '1' returned wrong value: "
"status=%d, value=%d\n",
int_status, retrieved_int);
}
} else {
printf("FAIL: napi_get_named_property with key '1' failed: %d\n",
get_status);
}
// Test with napi_get_property using numeric string keys
napi_value key_0, key_1;
NODE_API_CALL(env,
napi_create_string_utf8(env, "0", NAPI_AUTO_LENGTH, &key_0));
NODE_API_CALL(env,
napi_create_string_utf8(env, "1", NAPI_AUTO_LENGTH, &key_1));
napi_value prop_value;
napi_status prop_status = napi_get_property(env, obj, key_0, &prop_value);
if (prop_status == napi_ok) {
int32_t prop_int;
napi_status int_status = napi_get_value_int32(env, prop_value, &prop_int);
if (int_status == napi_ok && prop_int == 123) {
printf(
"PASS: napi_get_property with key '0' returned correct value: %d\n",
prop_int);
} else {
printf("FAIL: napi_get_property with key '0' returned wrong value: "
"status=%d, value=%d\n",
int_status, prop_int);
}
} else {
printf("FAIL: napi_get_property with key '0' failed: %d\n", prop_status);
}
// Test napi_has_property
bool has_property;
napi_status has_prop_status =
napi_has_property(env, obj, key_1, &has_property);
if (has_prop_status == napi_ok && has_property) {
printf("PASS: napi_has_property with key '1' returned true\n");
} else {
printf("FAIL: napi_has_property with key '1' failed or returned false: "
"status=%d, has=%s\n",
has_prop_status, has_property ? "true" : "false");
}
// Test napi_has_own_property
bool has_own_property;
napi_status has_own_status =
napi_has_own_property(env, obj, key_0, &has_own_property);
if (has_own_status == napi_ok && has_own_property) {
printf("PASS: napi_has_own_property with key '0' returned true\n");
} else {
printf("FAIL: napi_has_own_property with key '0' failed or returned false: "
"status=%d, has=%s\n",
has_own_status, has_own_property ? "true" : "false");
}
// Test napi_delete_property
bool delete_result;
napi_status delete_status =
napi_delete_property(env, obj, key_1, &delete_result);
if (delete_status == napi_ok) {
printf("PASS: napi_delete_property with key '1' succeeded, result=%s\n",
delete_result ? "true" : "false");
// Verify the property was actually deleted
bool still_has_property;
napi_status verify_status =
napi_has_property(env, obj, key_1, &still_has_property);
if (verify_status == napi_ok && !still_has_property) {
printf("PASS: Property '1' was successfully deleted\n");
} else {
printf(
"FAIL: Property '1' still exists after deletion: status=%d, has=%s\n",
verify_status, still_has_property ? "true" : "false");
}
} else {
printf("FAIL: napi_delete_property with key '1' failed: %d\n",
delete_status);
}
return ok(env);
}
static napi_value test_deferred_exceptions(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
auto do_throw = [&] {
if (!info.Env().IsExceptionPending()) {
Napi::Error::New(env,
"Creating empty object failed while exception pending")
.ThrowAsJavaScriptException();
}
};
auto clear = [&] { info.Env().GetAndClearPendingException(); };
auto expect_failure_during_exception = [&](const char *name, const auto &fn) {
do_throw();
napi_status status = fn();
if (status == napi_ok) {
printf("expected failure for %s, but got success\n", name);
return false;
}
clear();
status = fn();
if (status != napi_ok) {
printf("expected success for %s, but got failure (%d)\n", name, status);
return false;
}
return true;
};
do_throw();
napi_value object;
napi_status status = napi_create_object(env, &object);
if (status != napi_ok) {
printf("napi_create_object failed: %d\n", status);
return nullptr;
}
puts("napi_create_object succeeded");
napi_valuetype type;
status = napi_typeof(env, object, &type);
if (status != napi_ok) {
printf("napi_typeof failed: %d\n", status);
return nullptr;
}
if (type != napi_object) {
printf("napi_typeof produced %d\n", type);
return nullptr;
}
napi_value string;
status = napi_create_string_utf8(env, "hej", 3, &string);
if (status != napi_ok) {
printf("napi_create_string_utf8 failed: %d\n", status);
return nullptr;
}
status = napi_typeof(env, string, &type);
if (status != napi_ok) {
printf("napi_typeof failed: %d\n", status);
return nullptr;
}
if (type != napi_string) {
printf("napi_typeof produced %d\n", type);
return nullptr;
}
char buffer[4];
size_t written;
status =
napi_get_value_string_utf8(env, string, buffer, sizeof(buffer), &written);
if (status != napi_ok) {
printf("napi_get_value_string_utf8 failed: %d\n", status);
return nullptr;
}
if (sizeof(buffer) <= written) {
printf("retrieved too many characters: %zu\n", written);
return nullptr;
}
buffer[written] = '\0';
if (strcmp(buffer, "hej") != 0) {
printf("invalid string: \"%s\"\n", buffer);
return nullptr;
}
puts("string retrieval succeeded");
napi_value function;
expect_failure_during_exception("napi_create_function", [&] {
return napi_create_function(
env, "thing", 5,
+[](napi_env env, napi_callback_info info) {
puts("thing called");
return ok(env);
},
nullptr, &function);
});
napi_value result;
expect_failure_during_exception("napi_call_function", [&] {
return napi_call_function(env, function, function, 0, nullptr, &result);
});
expect_failure_during_exception("napi_set_named_property", [&] {
return napi_set_named_property(env, object, "hej", result);
});
expect_failure_during_exception("napi_get_named_property", [&] {
return napi_get_named_property(env, object, "hej", &result);
});
bool has_own_property;
expect_failure_during_exception("napi_has_own_property", [&] {
return napi_has_own_property(env, object, string, &has_own_property);
});
if (!has_own_property) {
puts("object does not have own property \"result\"");
return nullptr;
}
napi_value keys;
expect_failure_during_exception("napi_get_property_names", [&] {
return napi_get_property_names(env, object, &keys);
});
expect_failure_during_exception("napi_delete_property", [&] {
return napi_delete_property(env, object, string, nullptr);
});
expect_failure_during_exception("napi_has_own_property", [&] {
return napi_has_own_property(env, object, string, &has_own_property);
});
if (has_own_property) {
puts("object still has own property \"result\"");
return nullptr;
}
napi_property_descriptor desc[2]{
{
.utf8name = "foo",
.name = nullptr,
.method = nullptr,
.getter =
+[](napi_env env, napi_callback_info info) {
puts("foo getter");
napi_value result;
napi_create_int32(env, 42, &result);
return result;
},
.setter = nullptr,
.value = nullptr,
.attributes = static_cast<napi_property_attributes>(napi_default),
.data = nullptr,
},
{
.utf8name = "bar",
.name = nullptr,
.method = nullptr,
.getter = nullptr,
.setter =
+[](napi_env env, napi_callback_info info) {
size_t argc = 0;
assert(napi_ok == napi_get_cb_info(env, info, &argc, nullptr,
nullptr, nullptr));
printf("bar setter: argc == %zu\n", argc);
assert(argc == 1);
return ok(env);
},
.value = nullptr,
.attributes = static_cast<napi_property_attributes>(napi_default |
napi_writable),
.data = nullptr,
},
};
expect_failure_during_exception("napi_define_properties", [&] {
return napi_define_properties(env, object, 2, desc);
});
do_throw();
napi_value two;
status = napi_create_int32(env, 2, &two);
if (status != napi_ok) {
printf("napi_create_int32 failed: %d\n", status);
return nullptr;
}
expect_failure_during_exception("napi_set_element", [&] {
return napi_set_element(env, object, 0, two);
});
expect_failure_during_exception("napi_get_named_property", [&] {
return napi_get_named_property(env, object, "foo", &result);
});
do_throw();
int32_t n;
status = napi_get_value_int32(env, result, &n);
if (status != napi_ok) {
printf("napi_get_value_int32 failed: %d\n", status);
return nullptr;
}
assert(n == 42);
expect_failure_during_exception("napi_set_named_property", [&] {
return napi_set_named_property(env, object, "bar", result);
});
clear();
status = napi_wrap(
env, object, nullptr,
+[](napi_env env, void *data, void *finalize_hint) {
puts("finalizer start");
printf("napi_throw status: %d\n", napi_throw(env, ok(env)));
puts("finalizer end");
},
nullptr, nullptr);
if (status != napi_ok) {
printf("napi_wrap failed: %d\n", status);
return nullptr;
}
clear();
puts("ok");
return ok(env);
}
// Test for napi_create_array_with_length boundary handling
// Bun converts out-of-bounds lengths to 0, Node may handle differently
static napi_value
test_napi_create_array_boundary(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test with negative length
napi_value array_neg;
napi_status status = napi_create_array_with_length(env, -1, &array_neg);
if (status == napi_ok) {
uint32_t length;
NODE_API_CALL(env, napi_get_array_length(env, array_neg, &length));
printf("PASS: napi_create_array_with_length(-1) created array with length "
"%u\n",
length);
} else {
printf("FAIL: napi_create_array_with_length(-1) failed with status %d\n",
status);
}
// Test with very large length (larger than max u32)
napi_value array_large;
size_t huge_length = (size_t)0xFFFFFFFF + 100;
status = napi_create_array_with_length(env, huge_length, &array_large);
if (status == napi_ok) {
uint32_t length;
NODE_API_CALL(env, napi_get_array_length(env, array_large, &length));
printf("PASS: napi_create_array_with_length(0x%zx) created array with "
"length %u\n",
huge_length, length);
} else if (status == napi_invalid_arg || status == napi_generic_failure) {
printf(
"PASS: napi_create_array_with_length(0x%zx) rejected with status %d\n",
huge_length, status);
} else {
printf("FAIL: napi_create_array_with_length(0x%zx) returned unexpected "
"status %d\n",
huge_length, status);
}
// Test with value that becomes negative when cast to i32 (should become 0)
napi_value array_negative;
size_t negative_when_signed = 0x80000000; // 2^31 - becomes negative in i32
status =
napi_create_array_with_length(env, negative_when_signed, &array_negative);
if (status == napi_ok) {
uint32_t length;
NODE_API_CALL(env, napi_get_array_length(env, array_negative, &length));
if (length == 0) {
printf("PASS: napi_create_array_with_length(0x%zx) created array with "
"length 0 (clamped negative)\n",
negative_when_signed);
} else {
printf("FAIL: napi_create_array_with_length(0x%zx) created array with "
"length %u (expected 0)\n",
negative_when_signed, length);
}
} else {
printf("FAIL: napi_create_array_with_length(0x%zx) failed with status %d\n",
negative_when_signed, status);
}
// Test with normal length to ensure it still works
napi_value array_normal;
status = napi_create_array_with_length(env, 10, &array_normal);
if (status == napi_ok) {
uint32_t length;
NODE_API_CALL(env, napi_get_array_length(env, array_normal, &length));
if (length == 10) {
printf("PASS: napi_create_array_with_length(10) created array with "
"correct length\n");
} else {
printf("FAIL: napi_create_array_with_length(10) created array with "
"length %u\n",
length);
}
} else {
printf("FAIL: napi_create_array_with_length(10) failed with status %d\n",
status);
}
return ok(env);
}
// Test for napi_call_function recv parameter validation
// Node validates recv parameter, Bun might not
static napi_value
test_napi_call_function_recv_null(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Create a simple function
napi_value global, function_val;
NODE_API_CALL(env, napi_get_global(env, &global));
// Get Array constructor as our test function
napi_value array_constructor;
NODE_API_CALL(
env, napi_get_named_property(env, global, "Array", &array_constructor));
// Try to call with null recv (this) parameter
napi_value result;
napi_status status =
napi_call_function(env, nullptr, array_constructor, 0, nullptr, &result);
if (status == napi_ok) {
printf("PASS: napi_call_function with null recv succeeded\n");
} else if (status == napi_invalid_arg) {
printf(
"PASS: napi_call_function with null recv returned napi_invalid_arg\n");
} else {
printf("FAIL: napi_call_function with null recv returned unexpected "
"status: %d\n",
status);
}
// Also test with a valid recv to ensure normal operation works
status =
napi_call_function(env, global, array_constructor, 0, nullptr, &result);
if (status == napi_ok) {
printf("PASS: napi_call_function with valid recv succeeded\n");
} else {
printf("FAIL: napi_call_function with valid recv failed with status: %d\n",
status);
}
return ok(env);
}
// Test for napi_strict_equals - should match JavaScript === operator behavior
// This tests that NaN !== NaN and -0 === 0
static napi_value test_napi_strict_equals(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test NaN !== NaN
napi_value nan1, nan2;
NODE_API_CALL(env, napi_create_double(
env, std::numeric_limits<double>::quiet_NaN(), &nan1));
NODE_API_CALL(env, napi_create_double(
env, std::numeric_limits<double>::quiet_NaN(), &nan2));
bool nan_equals;
NODE_API_CALL(env, napi_strict_equals(env, nan1, nan2, &nan_equals));
if (nan_equals) {
printf("FAIL: NaN === NaN returned true, expected false\n");
} else {
printf("PASS: NaN !== NaN\n");
}
// Test -0 === 0
napi_value neg_zero, pos_zero;
NODE_API_CALL(env, napi_create_double(env, -0.0, &neg_zero));
NODE_API_CALL(env, napi_create_double(env, 0.0, &pos_zero));
bool zero_equals;
NODE_API_CALL(env, napi_strict_equals(env, neg_zero, pos_zero, &zero_equals));
if (!zero_equals) {
printf("FAIL: -0 === 0 returned false, expected true\n");
} else {
printf("PASS: -0 === 0\n");
}
// Test normal values work correctly
napi_value val1, val2, val3;
NODE_API_CALL(env, napi_create_double(env, 42.0, &val1));
NODE_API_CALL(env, napi_create_double(env, 42.0, &val2));
NODE_API_CALL(env, napi_create_double(env, 43.0, &val3));
bool same_equals, diff_equals;
NODE_API_CALL(env, napi_strict_equals(env, val1, val2, &same_equals));
NODE_API_CALL(env, napi_strict_equals(env, val1, val3, &diff_equals));
if (!same_equals) {
printf("FAIL: 42 === 42 returned false, expected true\n");
} else {
printf("PASS: 42 === 42\n");
}
if (diff_equals) {
printf("FAIL: 42 === 43 returned true, expected false\n");
} else {
printf("PASS: 42 !== 43\n");
}
return ok(env);
}
// Test for dataview bounds checking and error messages
static napi_value
test_napi_dataview_bounds_errors(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Create an ArrayBuffer
napi_value arraybuffer;
void *data = nullptr;
NODE_API_CALL(env, napi_create_arraybuffer(env, 100, &data, &arraybuffer));
// Test 1: DataView exceeding buffer bounds
napi_value dataview;
napi_status status = napi_create_dataview(env, 50, arraybuffer, 60,
&dataview); // 60 + 50 = 110 > 100
if (status == napi_ok) {
printf("FAIL: napi_create_dataview allowed DataView exceeding buffer "
"bounds\n");
} else {
printf("PASS: napi_create_dataview rejected DataView exceeding buffer "
"bounds\n");
// Check if an exception was thrown with the expected error
bool is_exception_pending = false;
NODE_API_CALL(env, napi_is_exception_pending(env, &is_exception_pending));
if (is_exception_pending) {
napi_value exception;
NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &exception));
// Try to get error message
napi_value message_val;
napi_status msg_status =
napi_get_named_property(env, exception, "message", &message_val);
if (msg_status == napi_ok) {
char message[256];
size_t message_len;
napi_get_value_string_utf8(env, message_val, message, sizeof(message),
&message_len);
printf(" Error message: %s\n", message);
}
}
}
// Test 2: DataView at exact boundary (should work)
napi_value boundary_dataview;
status = napi_create_dataview(env, 40, arraybuffer, 60,
&boundary_dataview); // 60 + 40 = 100 exactly
if (status != napi_ok) {
printf("FAIL: napi_create_dataview rejected valid DataView at exact "
"boundary\n");
} else {
printf("PASS: napi_create_dataview accepted valid DataView at exact "
"boundary\n");
}
// Test 3: DataView with offset beyond buffer
napi_value beyond_dataview;
status = napi_create_dataview(env, 1, arraybuffer, 101,
&beyond_dataview); // offset 101 > 100
if (status == napi_ok) {
printf("FAIL: napi_create_dataview allowed DataView with offset beyond "
"buffer\n");
} else {
printf("PASS: napi_create_dataview rejected DataView with offset beyond "
"buffer\n");
}
return ok(env);
}
// Test for napi_typeof with potentially empty/invalid values
static napi_value test_napi_typeof_empty_value(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test 1: Create an uninitialized napi_value (simulating empty JSValue)
// This is technically undefined behavior but can reveal differences
napi_value uninit_value;
memset(&uninit_value, 0, sizeof(uninit_value));
napi_valuetype type;
napi_status status = napi_typeof(env, uninit_value, &type);
if (status == napi_ok) {
if (type == napi_undefined) {
printf("PASS: napi_typeof(zero-initialized value) returned "
"napi_undefined (Bun behavior)\n");
} else {
printf("FAIL: napi_typeof(zero-initialized value) returned %d\n", type);
}
} else {
printf("PASS: napi_typeof(zero-initialized value) returned error status %d "
"(Node behavior)\n",
status);
}
// Test 2: Try accessing deleted reference (undefined behavior per spec)
// This is actually undefined behavior according to N-API documentation
// Both Node.js and Bun may crash or behave unpredictably
printf("INFO: Accessing deleted reference is undefined behavior - test "
"skipped\n");
// After napi_delete_reference, the ref is invalid and should not be used
// Test 3: Check with reinterpret_cast of nullptr
// This is the most likely way to get an empty JSValue
napi_value *null_ptr = nullptr;
napi_value null_value = reinterpret_cast<napi_value>(null_ptr);
status = napi_typeof(env, null_value, &type);
if (status == napi_ok) {
if (type == napi_undefined) {
printf("WARN: napi_typeof(nullptr) returned napi_undefined - Bun's "
"isEmpty() check\n");
} else {
printf("INFO: napi_typeof(nullptr) returned type %d\n", type);
}
} else {
printf("INFO: napi_typeof(nullptr) returned error %d (safer behavior)\n",
status);
}
return ok(env);
}
// Test for Object.freeze and Object.seal with indexed properties
static napi_value
test_napi_freeze_seal_indexed(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test 1: Freeze array (has indexed properties)
napi_value array;
NODE_API_CALL(env, napi_create_array_with_length(env, 3, &array));
// Set some values
napi_value val;
NODE_API_CALL(env, napi_create_int32(env, 42, &val));
NODE_API_CALL(env, napi_set_element(env, array, 0, val));
// Try to freeze the array
napi_status freeze_status = napi_object_freeze(env, array);
if (freeze_status == napi_ok) {
// Try to modify after freeze
napi_value new_val;
NODE_API_CALL(env, napi_create_int32(env, 99, &new_val));
napi_status set_status = napi_set_element(env, array, 1, new_val);
if (set_status != napi_ok) {
printf("PASS: Array was frozen - cannot modify elements\n");
} else {
// Check if it actually changed
napi_value get_val;
NODE_API_CALL(env, napi_get_element(env, array, 1, &get_val));
int32_t num;
NODE_API_CALL(env, napi_get_value_int32(env, get_val, &num));
if (num == 99) {
printf("FAIL: Array with indexed properties was NOT actually frozen "
"(Bun behavior?)\n");
} else {
printf("INFO: Array freeze had partial effect\n");
}
}
} else {
printf("INFO: napi_object_freeze failed on array with status %d\n",
freeze_status);
}
// Test 2: Seal array (has indexed properties)
napi_value array2;
NODE_API_CALL(env, napi_create_array_with_length(env, 3, &array2));
NODE_API_CALL(env, napi_set_element(env, array2, 0, val));
// Try to seal the array
napi_status seal_status = napi_object_seal(env, array2);
if (seal_status == napi_ok) {
// Try to add new property after seal
napi_value prop_val;
NODE_API_CALL(
env, napi_create_string_utf8(env, "test", NAPI_AUTO_LENGTH, &prop_val));
napi_status set_status =
napi_set_named_property(env, array2, "newProp", prop_val);
if (set_status != napi_ok) {
printf("PASS: Array was sealed - cannot add new properties\n");
} else {
// Check if it actually was added
napi_value get_prop;
napi_status get_status =
napi_get_named_property(env, array2, "newProp", &get_prop);
if (get_status == napi_ok) {
printf("FAIL: Array with indexed properties was NOT actually sealed "
"(Bun behavior?)\n");
} else {
printf("INFO: Array seal had partial effect\n");
}
}
} else {
printf("INFO: napi_object_seal failed on array with status %d\n",
seal_status);
}
// Test 3: Freeze regular object (no indexed properties)
napi_value obj;
NODE_API_CALL(env, napi_create_object(env, &obj));
NODE_API_CALL(env, napi_set_named_property(env, obj, "prop", val));
napi_status obj_freeze_status = napi_object_freeze(env, obj);
if (obj_freeze_status == napi_ok) {
// Try to modify after freeze
napi_value new_val;
NODE_API_CALL(env, napi_create_int32(env, 999, &new_val));
napi_status set_status = napi_set_named_property(env, obj, "prop", new_val);
if (set_status != napi_ok) {
printf("PASS: Regular object was frozen correctly\n");
} else {
// Check if it actually changed
napi_value get_val;
NODE_API_CALL(env, napi_get_named_property(env, obj, "prop", &get_val));
int32_t num;
NODE_API_CALL(env, napi_get_value_int32(env, get_val, &num));
if (num == 999) {
printf("FAIL: Regular object was not frozen\n");
} else {
printf("PASS: Regular object freeze prevented modification\n");
}
}
}
return ok(env);
}
// Test for napi_create_external_buffer with empty/null data
static void empty_buffer_finalizer(napi_env env, void *data, void *hint) {
// No-op finalizer for empty buffers
}
static napi_value
test_napi_create_external_buffer_empty(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test 1: nullptr data with zero length
{
napi_value buffer;
napi_status status = napi_create_external_buffer(
env, 0, nullptr, empty_buffer_finalizer, nullptr, &buffer);
if (status != napi_ok) {
printf("FAIL: napi_create_external_buffer with nullptr and zero length "
"failed with status %d\n",
status);
return env.Undefined();
}
// Verify it's a buffer
bool is_buffer;
NODE_API_CALL(env, napi_is_buffer(env, buffer, &is_buffer));
if (!is_buffer) {
printf("FAIL: Created value is not a buffer\n");
return env.Undefined();
}
// Verify length is 0
size_t length;
void *data;
NODE_API_CALL(env, napi_get_buffer_info(env, buffer, &data, &length));
if (length != 0) {
printf("FAIL: Buffer length is %zu instead of 0\n", length);
return env.Undefined();
}
printf("PASS: napi_create_external_buffer with nullptr and zero length\n");
}
// Test 2: non-null data with zero length
{
char dummy = 0;
napi_value buffer;
napi_status status = napi_create_external_buffer(
env, 0, &dummy, empty_buffer_finalizer, nullptr, &buffer);
if (status != napi_ok) {
printf("FAIL: napi_create_external_buffer with non-null data and zero "
"length failed with status %d\n",
status);
return env.Undefined();
}
// Verify it's a buffer
bool is_buffer;
NODE_API_CALL(env, napi_is_buffer(env, buffer, &is_buffer));
if (!is_buffer) {
printf("FAIL: Created value is not a buffer\n");
return env.Undefined();
}
// Verify length is 0
size_t length;
void *data;
NODE_API_CALL(env, napi_get_buffer_info(env, buffer, &data, &length));
if (length != 0) {
printf("FAIL: Buffer length is %zu instead of 0\n", length);
return env.Undefined();
}
printf("PASS: napi_create_external_buffer with non-null data and zero "
"length\n");
}
// Test 3: nullptr finalizer
{
char dummy = 0;
napi_value buffer;
napi_status status =
napi_create_external_buffer(env, 0, &dummy, nullptr, nullptr, &buffer);
if (status != napi_ok) {
printf("FAIL: napi_create_external_buffer with nullptr finalizer failed "
"with status %d\n",
status);
return env.Undefined();
}
printf("PASS: napi_create_external_buffer with nullptr finalizer\n");
}
return ok(env);
}
static napi_value test_napi_empty_buffer_info(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
// Test: Create an empty external buffer and verify napi_get_buffer_info and
// napi_get_typedarray_info
{
napi_value buffer;
napi_status status =
napi_create_external_buffer(env, 0, nullptr, nullptr, nullptr, &buffer);
if (status != napi_ok) {
printf("FAIL: napi_create_external_buffer with nullptr and zero length "
"failed with status %d\n",
status);
return env.Undefined();
}
// Test napi_get_buffer_info
void *buffer_data = reinterpret_cast<void *>(
0xDEADBEEF); // Initialize to non-null to ensure it's set to null
size_t buffer_length =
999; // Initialize to non-zero to ensure it's set to 0
status = napi_get_buffer_info(env, buffer, &buffer_data, &buffer_length);
if (status != napi_ok) {
printf("FAIL: napi_get_buffer_info failed with status %d\n", status);
return env.Undefined();
}
if (buffer_data != nullptr) {
printf("FAIL: napi_get_buffer_info returned non-null data pointer: %p\n",
buffer_data);
return env.Undefined();
}
if (buffer_length != 0) {
printf("FAIL: napi_get_buffer_info returned non-zero length: %zu\n",
buffer_length);
return env.Undefined();
}
printf("PASS: napi_get_buffer_info returns null pointer and 0 length for "
"empty buffer\n");
// Test napi_get_typedarray_info
napi_typedarray_type type;
size_t typedarray_length = 999; // Initialize to non-zero
void *typedarray_data =
reinterpret_cast<void *>(0xDEADBEEF); // Initialize to non-null
napi_value arraybuffer;
size_t byte_offset;
status =
napi_get_typedarray_info(env, buffer, &type, &typedarray_length,
&typedarray_data, &arraybuffer, &byte_offset);
if (status != napi_ok) {
printf("FAIL: napi_get_typedarray_info failed with status %d\n", status);
return env.Undefined();
}
if (typedarray_data != nullptr) {
printf(
"FAIL: napi_get_typedarray_info returned non-null data pointer: %p\n",
typedarray_data);
return env.Undefined();
}
if (typedarray_length != 0) {
printf("FAIL: napi_get_typedarray_info returned non-zero length: %zu\n",
typedarray_length);
return env.Undefined();
}
printf("PASS: napi_get_typedarray_info returns null pointer and 0 length "
"for empty buffer\n");
// Test napi_is_detached_arraybuffer
// First get the underlying arraybuffer from the buffer
napi_value arraybuffer_from_buffer;
status = napi_get_typedarray_info(env, buffer, nullptr, nullptr, nullptr,
&arraybuffer_from_buffer, nullptr);
if (status != napi_ok) {
printf("FAIL: Could not get arraybuffer from buffer, status %d\n",
status);
return env.Undefined();
}
bool is_detached = false;
status = napi_is_detached_arraybuffer(env, arraybuffer_from_buffer,
&is_detached);
if (status != napi_ok) {
printf("FAIL: napi_is_detached_arraybuffer failed with status %d\n",
status);
return env.Undefined();
}
if (!is_detached) {
printf("FAIL: napi_is_detached_arraybuffer returned false for empty "
"buffer's arraybuffer, expected true\n");
return env.Undefined();
}
printf("PASS: napi_is_detached_arraybuffer returns true for empty buffer's "
"arraybuffer\n");
}
return ok(env);
}
void register_standalone_tests(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, test_issue_7685);
REGISTER_FUNCTION(env, exports, test_issue_11949);
REGISTER_FUNCTION(env, exports, test_napi_get_value_string_utf8_with_buffer);
REGISTER_FUNCTION(env, exports,
test_napi_threadsafe_function_does_not_hang_after_finalize);
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_string);
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_bigint);
REGISTER_FUNCTION(env, exports, test_napi_delete_property);
REGISTER_FUNCTION(env, exports, test_napi_escapable_handle_scope);
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_nesting);
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_many_args);
REGISTER_FUNCTION(env, exports, test_napi_ref);
REGISTER_FUNCTION(env, exports, test_napi_run_script);
REGISTER_FUNCTION(env, exports, test_napi_throw_with_nullptr);
REGISTER_FUNCTION(env, exports, test_extended_error_messages);
REGISTER_FUNCTION(env, exports, bigint_to_i64);
REGISTER_FUNCTION(env, exports, bigint_to_u64);
REGISTER_FUNCTION(env, exports, bigint_to_64_null);
REGISTER_FUNCTION(env, exports, test_is_buffer);
REGISTER_FUNCTION(env, exports, test_is_typedarray);
REGISTER_FUNCTION(env, exports, test_napi_get_default_values);
REGISTER_FUNCTION(env, exports, test_napi_numeric_string_keys);
REGISTER_FUNCTION(env, exports, test_deferred_exceptions);
REGISTER_FUNCTION(env, exports, test_napi_strict_equals);
REGISTER_FUNCTION(env, exports, test_napi_call_function_recv_null);
REGISTER_FUNCTION(env, exports, test_napi_create_array_boundary);
REGISTER_FUNCTION(env, exports, test_napi_dataview_bounds_errors);
REGISTER_FUNCTION(env, exports, test_napi_typeof_empty_value);
REGISTER_FUNCTION(env, exports, test_napi_freeze_seal_indexed);
REGISTER_FUNCTION(env, exports, test_napi_create_external_buffer_empty);
REGISTER_FUNCTION(env, exports, test_napi_empty_buffer_info);
}
} // namespace napitests