summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-12 13:46:50 -0700
committerGitHub <noreply@github.com>2024-02-12 13:46:50 -0700
commitf60720090c7bd8cdf91d7aebd0c42e01c86b3b83 (patch)
tree9becb7ff7e40d37769601fa049beccd101d58a98 /tests
parentbd1358efab8ba7339a8e70034315fa7da840292e (diff)
chore: move test_ffi and test_nap to tests/ [WIP] (#22394)
Moving some additional NAPI and. FFI tests out of the tree root.
Diffstat (limited to 'tests')
-rw-r--r--tests/config/deno.json3
-rw-r--r--tests/ffi/Cargo.toml17
-rw-r--r--tests/ffi/README.md1
-rw-r--r--tests/ffi/src/lib.rs559
-rw-r--r--tests/ffi/tests/bench.js687
-rw-r--r--tests/ffi/tests/event_loop_integration.ts78
-rw-r--r--tests/ffi/tests/ffi_callback_errors.ts141
-rw-r--r--tests/ffi/tests/ffi_types.ts529
-rw-r--r--tests/ffi/tests/integration_tests.rs302
-rw-r--r--tests/ffi/tests/test.js802
-rw-r--r--tests/ffi/tests/thread_safe_test.js105
-rw-r--r--tests/ffi/tests/thread_safe_test_worker.js41
-rw-r--r--tests/integration/js_unit_tests.rs2
-rw-r--r--tests/integration/node_compat_tests.rs3
-rw-r--r--tests/integration/node_unit_tests.rs3
-rw-r--r--tests/napi/.gitignore7
-rw-r--r--tests/napi/Cargo.toml22
-rw-r--r--tests/napi/array_test.js19
-rw-r--r--tests/napi/arraybuffer_test.js24
-rw-r--r--tests/napi/async_test.js16
-rw-r--r--tests/napi/bigint_test.js63
-rw-r--r--tests/napi/build.rs7
-rw-r--r--tests/napi/callback_test.js38
-rw-r--r--tests/napi/cleanup_hook_test.js36
-rw-r--r--tests/napi/coerce_test.js74
-rw-r--r--tests/napi/common.js28
-rw-r--r--tests/napi/date_test.js17
-rw-r--r--tests/napi/env_test.js10
-rw-r--r--tests/napi/error_test.js215
-rw-r--r--tests/napi/init_test.js14
-rw-r--r--tests/napi/make_callback_test.js53
-rw-r--r--tests/napi/mem_test.js11
-rw-r--r--tests/napi/module.c68
-rw-r--r--tests/napi/numbers_test.js18
-rw-r--r--tests/napi/object_wrap_test.js41
-rw-r--r--tests/napi/promise_test.js34
-rw-r--r--tests/napi/properties_test.js24
-rw-r--r--tests/napi/src/array.rs73
-rw-r--r--tests/napi/src/arraybuffer.rs52
-rw-r--r--tests/napi/src/async.rs114
-rw-r--r--tests/napi/src/bigint.rs205
-rw-r--r--tests/napi/src/callback.rs117
-rw-r--r--tests/napi/src/coerce.rs70
-rw-r--r--tests/napi/src/date.rs74
-rw-r--r--tests/napi/src/env.rs31
-rw-r--r--tests/napi/src/error.rs288
-rw-r--r--tests/napi/src/finalizer.rs141
-rw-r--r--tests/napi/src/lib.rs171
-rw-r--r--tests/napi/src/make_callback.rs85
-rw-r--r--tests/napi/src/mem.rs34
-rw-r--r--tests/napi/src/numbers.rs60
-rw-r--r--tests/napi/src/object_wrap.rs156
-rw-r--r--tests/napi/src/primitives.rs30
-rw-r--r--tests/napi/src/promise.rs74
-rw-r--r--tests/napi/src/properties.rs113
-rw-r--r--tests/napi/src/strings.rs49
-rw-r--r--tests/napi/src/symbol.rs39
-rw-r--r--tests/napi/src/tsfn.rs108
-rw-r--r--tests/napi/src/typedarray.rs157
-rw-r--r--tests/napi/strings_test.js15
-rw-r--r--tests/napi/symbol_test.js49
-rw-r--r--tests/napi/tests/napi_tests.rs87
-rw-r--r--tests/napi/typedarray_test.js39
63 files changed, 6539 insertions, 4 deletions
diff --git a/tests/config/deno.json b/tests/config/deno.json
index ec93111fd..52538a812 100644
--- a/tests/config/deno.json
+++ b/tests/config/deno.json
@@ -1,5 +1,6 @@
{
"imports": {
- "@test_util/": "../../test_util/"
+ "@test_util/": "../../test_util/",
+ "@std/": "../../test_util/std/"
}
}
diff --git a/tests/ffi/Cargo.toml b/tests/ffi/Cargo.toml
new file mode 100644
index 000000000..a5d2883ef
--- /dev/null
+++ b/tests/ffi/Cargo.toml
@@ -0,0 +1,17 @@
+# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "test_ffi"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+publish = false
+repository.workspace = true
+
+[lib]
+crate-type = ["cdylib"]
+
+[dev-dependencies]
+pretty_assertions.workspace = true
+test_util.workspace = true
diff --git a/tests/ffi/README.md b/tests/ffi/README.md
new file mode 100644
index 000000000..685385e4f
--- /dev/null
+++ b/tests/ffi/README.md
@@ -0,0 +1 @@
+# `test_ffi` crate
diff --git a/tests/ffi/src/lib.rs b/tests/ffi/src/lib.rs
new file mode 100644
index 000000000..f6ee31eb8
--- /dev/null
+++ b/tests/ffi/src/lib.rs
@@ -0,0 +1,559 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+use std::os::raw::c_void;
+use std::thread::sleep;
+use std::time::Duration;
+
+static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
+
+#[no_mangle]
+pub extern "C" fn print_something() {
+ println!("something");
+}
+
+/// # Safety
+///
+/// The pointer to the buffer must be valid and initialized, and the length must
+/// not be longer than the buffer's allocation.
+#[no_mangle]
+pub unsafe extern "C" fn print_buffer(ptr: *const u8, len: usize) {
+ let buf = std::slice::from_raw_parts(ptr, len);
+ println!("{buf:?}");
+}
+
+/// # Safety
+///
+/// The pointer to the buffer must be valid and initialized, and the length must
+/// not be longer than the buffer's allocation.
+#[no_mangle]
+pub unsafe extern "C" fn print_buffer2(
+ ptr1: *const u8,
+ len1: usize,
+ ptr2: *const u8,
+ len2: usize,
+) {
+ let buf1 = std::slice::from_raw_parts(ptr1, len1);
+ let buf2 = std::slice::from_raw_parts(ptr2, len2);
+ println!("{buf1:?} {buf2:?}");
+}
+
+#[no_mangle]
+pub extern "C" fn return_buffer() -> *const u8 {
+ BUFFER.as_ptr()
+}
+
+#[no_mangle]
+pub extern "C" fn is_null_ptr(ptr: *const u8) -> bool {
+ ptr.is_null()
+}
+
+#[no_mangle]
+pub extern "C" fn add_u32(a: u32, b: u32) -> u32 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_i32(a: i32, b: i32) -> i32 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_u64(a: u64, b: u64) -> u64 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_i64(a: i64, b: i64) -> i64 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_usize(a: usize, b: usize) -> usize {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_usize_fast(a: usize, b: usize) -> u32 {
+ (a + b) as u32
+}
+
+#[no_mangle]
+pub extern "C" fn add_isize(a: isize, b: isize) -> isize {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_f32(a: f32, b: f32) -> f32 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn add_f64(a: f64, b: f64) -> f64 {
+ a + b
+}
+
+#[no_mangle]
+pub extern "C" fn and(a: bool, b: bool) -> bool {
+ a && b
+}
+
+#[no_mangle]
+unsafe extern "C" fn hash(ptr: *const u8, length: u32) -> u32 {
+ let buf = std::slice::from_raw_parts(ptr, length as usize);
+ let mut hash: u32 = 0;
+ for byte in buf {
+ hash = hash.wrapping_mul(0x10001000).wrapping_add(*byte as u32);
+ }
+ hash
+}
+
+#[no_mangle]
+pub extern "C" fn sleep_blocking(ms: u64) {
+ let duration = Duration::from_millis(ms);
+ sleep(duration);
+}
+
+/// # Safety
+///
+/// The pointer to the buffer must be valid and initialized, and the length must
+/// not be longer than the buffer's allocation.
+#[no_mangle]
+pub unsafe extern "C" fn fill_buffer(value: u8, buf: *mut u8, len: usize) {
+ let buf = std::slice::from_raw_parts_mut(buf, len);
+ for itm in buf.iter_mut() {
+ *itm = value;
+ }
+}
+
+/// # Safety
+///
+/// The pointer to the buffer must be valid and initialized, and the length must
+/// not be longer than the buffer's allocation.
+#[no_mangle]
+pub unsafe extern "C" fn nonblocking_buffer(ptr: *const u8, len: usize) {
+ let buf = std::slice::from_raw_parts(ptr, len);
+ assert_eq!(buf, vec![1, 2, 3, 4, 5, 6, 7, 8]);
+}
+
+#[no_mangle]
+pub extern "C" fn get_add_u32_ptr() -> *const c_void {
+ add_u32 as *const c_void
+}
+
+#[no_mangle]
+pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void {
+ sleep_blocking as *const c_void
+}
+
+#[no_mangle]
+pub extern "C" fn call_fn_ptr(func: Option<extern "C" fn()>) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ func();
+}
+
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_many_parameters(
+ func: Option<
+ extern "C" fn(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, *const u8),
+ >,
+) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ func(1, -1, 2, -2, 3, -3, 4, -4, 0.5, -0.5, BUFFER.as_ptr());
+}
+
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_return_u8(func: Option<extern "C" fn() -> u8>) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ println!("u8: {}", func());
+}
+
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_return_buffer(
+ func: Option<extern "C" fn() -> *const u8>,
+) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ let ptr = func();
+ let buf = unsafe { std::slice::from_raw_parts(ptr, 8) };
+ println!("buf: {buf:?}");
+}
+
+static mut STORED_FUNCTION: Option<extern "C" fn()> = None;
+static mut STORED_FUNCTION_2: Option<extern "C" fn(u8) -> u8> = None;
+
+#[no_mangle]
+pub extern "C" fn store_function(func: Option<extern "C" fn()>) {
+ unsafe { STORED_FUNCTION = func };
+ if func.is_none() {
+ println!("STORED_FUNCTION cleared");
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn store_function_2(func: Option<extern "C" fn(u8) -> u8>) {
+ unsafe { STORED_FUNCTION_2 = func };
+ if func.is_none() {
+ println!("STORED_FUNCTION_2 cleared");
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function() {
+ unsafe {
+ if STORED_FUNCTION.is_none() {
+ return;
+ }
+ STORED_FUNCTION.unwrap()();
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function_2(arg: u8) {
+ unsafe {
+ if STORED_FUNCTION_2.is_none() {
+ return;
+ }
+ println!("{}", STORED_FUNCTION_2.unwrap()(arg));
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function_thread_safe() {
+ std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(1500));
+ unsafe {
+ if STORED_FUNCTION.is_none() {
+ return;
+ }
+ STORED_FUNCTION.unwrap()();
+ }
+ });
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function_thread_safe_and_log() {
+ std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(1500));
+ unsafe {
+ if STORED_FUNCTION.is_none() {
+ return;
+ }
+ STORED_FUNCTION.unwrap()();
+ println!("STORED_FUNCTION called");
+ }
+ });
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function_2_thread_safe(arg: u8) {
+ std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(1500));
+ unsafe {
+ if STORED_FUNCTION_2.is_none() {
+ return;
+ }
+ println!("Calling");
+ STORED_FUNCTION_2.unwrap()(arg);
+ }
+ });
+}
+
+#[no_mangle]
+pub extern "C" fn log_many_parameters(
+ a: u8,
+ b: u16,
+ c: u32,
+ d: u64,
+ e: f64,
+ f: f32,
+ g: i64,
+ h: i32,
+ i: i16,
+ j: i8,
+ k: isize,
+ l: usize,
+ m: f64,
+ n: f32,
+ o: f64,
+ p: f32,
+ q: f64,
+ r: f32,
+ s: f64,
+) {
+ println!("{a} {b} {c} {d} {e} {f} {g} {h} {i} {j} {k} {l} {m} {n} {o} {p} {q} {r} {s}");
+}
+
+#[no_mangle]
+pub extern "C" fn cast_u8_u32(x: u8) -> u32 {
+ x as u32
+}
+
+#[no_mangle]
+pub extern "C" fn cast_u32_u8(x: u32) -> u8 {
+ x as u8
+}
+
+#[no_mangle]
+pub extern "C" fn add_many_u16(
+ a: u16,
+ b: u16,
+ c: u16,
+ d: u16,
+ e: u16,
+ f: u16,
+ g: u16,
+ h: u16,
+ i: u16,
+ j: u16,
+ k: u16,
+ l: u16,
+ m: u16,
+) -> u16 {
+ a + b + c + d + e + f + g + h + i + j + k + l + m
+}
+
+// FFI performance helper functions
+#[no_mangle]
+pub extern "C" fn nop() {}
+
+#[no_mangle]
+pub extern "C" fn nop_bool(_a: bool) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u8(_a: u8) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i8(_a: i8) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u16(_a: u16) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i16(_a: i16) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u32(_a: u32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i32(_a: i32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u64(_a: u64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i64(_a: i64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_usize(_a: usize) {}
+
+#[no_mangle]
+pub extern "C" fn nop_isize(_a: isize) {}
+
+#[no_mangle]
+pub extern "C" fn nop_f32(_a: f32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_f64(_a: f64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_buffer(_buffer: *mut [u8; 8]) {}
+
+#[no_mangle]
+pub extern "C" fn return_bool() -> bool {
+ true
+}
+
+#[no_mangle]
+pub extern "C" fn return_u8() -> u8 {
+ 255
+}
+
+#[no_mangle]
+pub extern "C" fn return_i8() -> i8 {
+ -128
+}
+
+#[no_mangle]
+pub extern "C" fn return_u16() -> u16 {
+ 65535
+}
+
+#[no_mangle]
+pub extern "C" fn return_i16() -> i16 {
+ -32768
+}
+
+#[no_mangle]
+pub extern "C" fn return_u32() -> u32 {
+ 4294967295
+}
+
+#[no_mangle]
+pub extern "C" fn return_i32() -> i32 {
+ -2147483648
+}
+
+#[no_mangle]
+pub extern "C" fn return_u64() -> u64 {
+ 18446744073709551615
+}
+
+#[no_mangle]
+pub extern "C" fn return_i64() -> i64 {
+ -9223372036854775808
+}
+
+#[no_mangle]
+pub extern "C" fn return_usize() -> usize {
+ 18446744073709551615
+}
+
+#[no_mangle]
+pub extern "C" fn return_isize() -> isize {
+ -9223372036854775808
+}
+
+#[no_mangle]
+pub extern "C" fn return_f32() -> f32 {
+ #[allow(clippy::excessive_precision)]
+ 0.20298023223876953125
+}
+
+#[no_mangle]
+pub extern "C" fn return_f64() -> f64 {
+ 1e-10
+}
+
+// Parameters iteration
+
+#[no_mangle]
+pub extern "C" fn nop_many_parameters(
+ _: u8,
+ _: i8,
+ _: u16,
+ _: i16,
+ _: u32,
+ _: i32,
+ _: u64,
+ _: i64,
+ _: usize,
+ _: isize,
+ _: f32,
+ _: f64,
+ _: *mut [u8; 8],
+ _: u8,
+ _: i8,
+ _: u16,
+ _: i16,
+ _: u32,
+ _: i32,
+ _: u64,
+ _: i64,
+ _: usize,
+ _: isize,
+ _: f32,
+ _: f64,
+ _: *mut [u8; 8],
+) {
+}
+
+// Statics
+#[no_mangle]
+pub static static_u32: u32 = 42;
+
+#[no_mangle]
+pub static static_i64: i64 = -1242464576485;
+
+#[repr(C)]
+pub struct Structure {
+ _data: u32,
+}
+
+#[no_mangle]
+pub static mut static_ptr: Structure = Structure { _data: 42 };
+
+static STRING: &str = "Hello, world!\0";
+
+#[no_mangle]
+extern "C" fn ffi_string() -> *const u8 {
+ STRING.as_ptr()
+}
+
+/// Invalid UTF-8 characters, array of length 14
+#[no_mangle]
+pub static static_char: [u8; 14] = [
+ 0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+ 0x00,
+];
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct Rect {
+ x: f64,
+ y: f64,
+ w: f64,
+ h: f64,
+}
+
+#[no_mangle]
+pub extern "C" fn make_rect(x: f64, y: f64, w: f64, h: f64) -> Rect {
+ Rect { x, y, w, h }
+}
+
+#[no_mangle]
+pub extern "C" fn print_rect(rect: Rect) {
+ println!("{rect:?}");
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct Mixed {
+ u8: u8,
+ f32: f32,
+ rect: Rect,
+ usize: usize,
+ array: [u32; 2],
+}
+
+/// # Safety
+///
+/// The array pointer to the buffer must be valid and initialized, and the length must
+/// be 2.
+#[no_mangle]
+pub unsafe extern "C" fn create_mixed(
+ u8: u8,
+ f32: f32,
+ rect: Rect,
+ usize: usize,
+ array: *const [u32; 2],
+) -> Mixed {
+ let array = *array
+ .as_ref()
+ .expect("Array parameter should contain value");
+ Mixed {
+ u8,
+ f32,
+ rect,
+ usize,
+ array,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn print_mixed(mixed: Mixed) {
+ println!("{mixed:?}");
+}
diff --git a/tests/ffi/tests/bench.js b/tests/ffi/tests/bench.js
new file mode 100644
index 000000000..49884d32e
--- /dev/null
+++ b/tests/ffi/tests/bench.js
@@ -0,0 +1,687 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(libPath, {
+ "nop": { parameters: [], result: "void" },
+ "add_u32": { parameters: ["u32", "u32"], result: "u32" },
+ "add_u64": { parameters: ["u64", "u64"], result: "u64" },
+ "ffi_string": { parameters: [], result: "pointer" },
+ "hash": { parameters: ["buffer", "u32"], result: "u32" },
+ "nop_bool": { parameters: ["bool"], result: "void" },
+ "nop_u8": { parameters: ["u8"], result: "void" },
+ "nop_i8": { parameters: ["i8"], result: "void" },
+ "nop_u16": { parameters: ["u16"], result: "void" },
+ "nop_i16": { parameters: ["i16"], result: "void" },
+ "nop_u32": { parameters: ["u32"], result: "void" },
+ "nop_i32": { parameters: ["i32"], result: "void" },
+ "nop_u64": { parameters: ["u64"], result: "void" },
+ "nop_i64": { parameters: ["i64"], result: "void" },
+ "nop_usize": { parameters: ["usize"], result: "void" },
+ "nop_isize": { parameters: ["isize"], result: "void" },
+ "nop_f32": { parameters: ["f32"], result: "void" },
+ "nop_f64": { parameters: ["f64"], result: "void" },
+ "nop_buffer": { parameters: ["buffer"], result: "void" },
+ "return_bool": { parameters: [], result: "bool" },
+ "return_u8": { parameters: [], result: "u8" },
+ "return_i8": { parameters: [], result: "i8" },
+ "return_u16": { parameters: [], result: "u16" },
+ "return_i16": { parameters: [], result: "i16" },
+ "return_u32": { parameters: [], result: "u32" },
+ "return_i32": { parameters: [], result: "i32" },
+ "return_u64": { parameters: [], result: "u64" },
+ "return_i64": { parameters: [], result: "i64" },
+ "return_usize": { parameters: [], result: "usize" },
+ "return_isize": { parameters: [], result: "isize" },
+ "return_f32": { parameters: [], result: "f32" },
+ "return_f64": { parameters: [], result: "f64" },
+ "return_buffer": { parameters: [], result: "buffer" },
+ // Nonblocking calls
+ "nop_nonblocking": { name: "nop", parameters: [], result: "void" },
+ "nop_bool_nonblocking": {
+ name: "nop_bool",
+ parameters: ["bool"],
+ result: "void",
+ },
+ "nop_u8_nonblocking": { name: "nop_u8", parameters: ["u8"], result: "void" },
+ "nop_i8_nonblocking": { name: "nop_i8", parameters: ["i8"], result: "void" },
+ "nop_u16_nonblocking": {
+ name: "nop_u16",
+ parameters: ["u16"],
+ result: "void",
+ },
+ "nop_i16_nonblocking": {
+ name: "nop_i16",
+ parameters: ["i16"],
+ result: "void",
+ },
+ "nop_u32_nonblocking": {
+ name: "nop_u32",
+ parameters: ["u32"],
+ result: "void",
+ },
+ "nop_i32_nonblocking": {
+ name: "nop_i32",
+ parameters: ["i32"],
+ result: "void",
+ },
+ "nop_u64_nonblocking": {
+ name: "nop_u64",
+ parameters: ["u64"],
+ result: "void",
+ },
+ "nop_i64_nonblocking": {
+ name: "nop_i64",
+ parameters: ["i64"],
+ result: "void",
+ },
+ "nop_usize_nonblocking": {
+ name: "nop_usize",
+ parameters: ["usize"],
+ result: "void",
+ },
+ "nop_isize_nonblocking": {
+ name: "nop_isize",
+ parameters: ["isize"],
+ result: "void",
+ },
+ "nop_f32_nonblocking": {
+ name: "nop_f32",
+ parameters: ["f32"],
+ result: "void",
+ },
+ "nop_f64_nonblocking": {
+ name: "nop_f64",
+ parameters: ["f64"],
+ result: "void",
+ },
+ "nop_buffer_nonblocking": {
+ name: "nop_buffer",
+ parameters: ["buffer"],
+ result: "void",
+ },
+ "return_bool_nonblocking": {
+ name: "return_bool",
+ parameters: [],
+ result: "bool",
+ },
+ "return_u8_nonblocking": { name: "return_u8", parameters: [], result: "u8" },
+ "return_i8_nonblocking": { name: "return_i8", parameters: [], result: "i8" },
+ "return_u16_nonblocking": {
+ name: "return_u16",
+ parameters: [],
+ result: "u16",
+ },
+ "return_i16_nonblocking": {
+ name: "return_i16",
+ parameters: [],
+ result: "i16",
+ },
+ "return_u32_nonblocking": {
+ name: "return_u32",
+ parameters: [],
+ result: "u32",
+ },
+ "return_i32_nonblocking": {
+ name: "return_i32",
+ parameters: [],
+ result: "i32",
+ },
+ "return_u64_nonblocking": {
+ name: "return_u64",
+ parameters: [],
+ result: "u64",
+ },
+ "return_i64_nonblocking": {
+ name: "return_i64",
+ parameters: [],
+ result: "i64",
+ },
+ "return_usize_nonblocking": {
+ name: "return_usize",
+ parameters: [],
+ result: "usize",
+ },
+ "return_isize_nonblocking": {
+ name: "return_isize",
+ parameters: [],
+ result: "isize",
+ },
+ "return_f32_nonblocking": {
+ name: "return_f32",
+ parameters: [],
+ result: "f32",
+ },
+ "return_f64_nonblocking": {
+ name: "return_f64",
+ parameters: [],
+ result: "f64",
+ },
+ "return_buffer_nonblocking": {
+ name: "return_buffer",
+ parameters: [],
+ result: "buffer",
+ },
+ // Parameter checking
+ "nop_many_parameters": {
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "buffer",
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "buffer",
+ ],
+ result: "void",
+ },
+ "nop_many_parameters_nonblocking": {
+ name: "nop_many_parameters",
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+ nonblocking: true,
+ },
+});
+
+const { nop } = dylib.symbols;
+Deno.bench("nop()", () => {
+ nop();
+});
+
+const bytes = new Uint8Array(64);
+
+const { hash } = dylib.symbols;
+Deno.bench("hash()", () => {
+ hash(bytes, bytes.byteLength);
+});
+
+const { ffi_string } = dylib.symbols;
+Deno.bench(
+ "c string",
+ () => Deno.UnsafePointerView.getCString(ffi_string()),
+);
+
+const { add_u32 } = dylib.symbols;
+Deno.bench("add_u32()", () => {
+ add_u32(1, 2);
+});
+
+const { return_buffer } = dylib.symbols;
+Deno.bench("return_buffer()", () => {
+ return_buffer();
+});
+
+const { add_u64 } = dylib.symbols;
+Deno.bench("add_u64()", () => {
+ add_u64(1, 2);
+});
+
+const { return_u64 } = dylib.symbols;
+Deno.bench("return_u64()", () => {
+ return_u64();
+});
+
+const { return_i64 } = dylib.symbols;
+Deno.bench("return_i64()", () => {
+ return_i64();
+});
+
+const { nop_bool } = dylib.symbols;
+Deno.bench("nop_bool()", () => {
+ nop_bool(true);
+});
+
+const { nop_u8 } = dylib.symbols;
+Deno.bench("nop_u8()", () => {
+ nop_u8(100);
+});
+
+const { nop_i8 } = dylib.symbols;
+Deno.bench("nop_i8()", () => {
+ nop_i8(100);
+});
+
+const { nop_u16 } = dylib.symbols;
+Deno.bench("nop_u16()", () => {
+ nop_u16(100);
+});
+
+const { nop_i16 } = dylib.symbols;
+Deno.bench("nop_i16()", () => {
+ nop_i16(100);
+});
+
+const { nop_u32 } = dylib.symbols;
+Deno.bench("nop_u32()", () => {
+ nop_u32(100);
+});
+
+const { nop_i32 } = dylib.symbols;
+Deno.bench("nop_i32()", () => {
+ nop_i32(100);
+});
+
+const { nop_u64 } = dylib.symbols;
+Deno.bench("nop_u64()", () => {
+ nop_u64(100);
+});
+
+const { nop_i64 } = dylib.symbols;
+Deno.bench("nop_i64()", () => {
+ nop_i64(100);
+});
+
+const { nop_usize } = dylib.symbols;
+Deno.bench("nop_usize() number", () => {
+ nop_usize(100);
+});
+
+Deno.bench("nop_usize() bigint", () => {
+ nop_usize(100n);
+});
+
+const { nop_isize } = dylib.symbols;
+Deno.bench("nop_isize() number", () => {
+ nop_isize(100);
+});
+
+Deno.bench("nop_isize() bigint", () => {
+ nop_isize(100n);
+});
+
+const { nop_f32 } = dylib.symbols;
+Deno.bench("nop_f32()", () => {
+ nop_f32(100.1);
+});
+
+const { nop_f64 } = dylib.symbols;
+Deno.bench("nop_f64()", () => {
+ nop_f64(100.1);
+});
+
+const { nop_buffer } = dylib.symbols;
+const buffer = new Uint8Array(8).fill(5);
+Deno.bench("nop_buffer()", () => {
+ nop_buffer(buffer);
+});
+
+const { return_bool } = dylib.symbols;
+Deno.bench("return_bool()", () => {
+ return_bool();
+});
+
+const { return_u8 } = dylib.symbols;
+Deno.bench("return_u8()", () => {
+ return_u8();
+});
+
+const { return_i8 } = dylib.symbols;
+Deno.bench("return_i8()", () => {
+ return_i8();
+});
+
+const { return_u16 } = dylib.symbols;
+Deno.bench("return_u16()", () => {
+ return_u16();
+});
+
+const { return_i16 } = dylib.symbols;
+Deno.bench("return_i16()", () => {
+ return_i16();
+});
+
+const { return_u32 } = dylib.symbols;
+Deno.bench("return_u32()", () => {
+ return_u32();
+});
+
+const { return_i32 } = dylib.symbols;
+Deno.bench("return_i32()", () => {
+ return_i32();
+});
+
+const { return_usize } = dylib.symbols;
+Deno.bench("return_usize()", () => {
+ return_usize();
+});
+
+const { return_isize } = dylib.symbols;
+Deno.bench("return_isize()", () => {
+ return_isize();
+});
+
+const { return_f32 } = dylib.symbols;
+Deno.bench("return_f32()", () => {
+ return_f32();
+});
+
+const { return_f64 } = dylib.symbols;
+Deno.bench("return_f64()", () => {
+ return_f64();
+});
+
+// Nonblocking calls
+
+const { nop_nonblocking } = dylib.symbols;
+Deno.bench("nop_nonblocking()", async () => {
+ await nop_nonblocking();
+});
+
+const { nop_bool_nonblocking } = dylib.symbols;
+Deno.bench("nop_bool_nonblocking()", async () => {
+ await nop_bool_nonblocking(true);
+});
+
+const { nop_u8_nonblocking } = dylib.symbols;
+Deno.bench("nop_u8_nonblocking()", async () => {
+ await nop_u8_nonblocking(100);
+});
+
+const { nop_i8_nonblocking } = dylib.symbols;
+Deno.bench("nop_i8_nonblocking()", async () => {
+ await nop_i8_nonblocking(100);
+});
+
+const { nop_u16_nonblocking } = dylib.symbols;
+Deno.bench("nop_u16_nonblocking()", async () => {
+ await nop_u16_nonblocking(100);
+});
+
+const { nop_i16_nonblocking } = dylib.symbols;
+Deno.bench("nop_i16_nonblocking()", async () => {
+ await nop_i16_nonblocking(100);
+});
+
+const { nop_u32_nonblocking } = dylib.symbols;
+Deno.bench("nop_u32_nonblocking()", async () => {
+ await nop_u32_nonblocking(100);
+});
+
+const { nop_i32_nonblocking } = dylib.symbols;
+
+Deno.bench("nop_i32_nonblocking()", async () => {
+ await nop_i32_nonblocking(100);
+});
+
+const { nop_u64_nonblocking } = dylib.symbols;
+Deno.bench("nop_u64_nonblocking()", async () => {
+ await nop_u64_nonblocking(100);
+});
+
+const { nop_i64_nonblocking } = dylib.symbols;
+Deno.bench("nop_i64_nonblocking()", async () => {
+ await nop_i64_nonblocking(100);
+});
+
+const { nop_usize_nonblocking } = dylib.symbols;
+Deno.bench("nop_usize_nonblocking()", async () => {
+ await nop_usize_nonblocking(100);
+});
+
+const { nop_isize_nonblocking } = dylib.symbols;
+Deno.bench("nop_isize_nonblocking()", async () => {
+ await nop_isize_nonblocking(100);
+});
+
+const { nop_f32_nonblocking } = dylib.symbols;
+Deno.bench("nop_f32_nonblocking()", async () => {
+ await nop_f32_nonblocking(100);
+});
+
+const { nop_f64_nonblocking } = dylib.symbols;
+Deno.bench("nop_f64_nonblocking()", async () => {
+ await nop_f64_nonblocking(100);
+});
+
+const { nop_buffer_nonblocking } = dylib.symbols;
+Deno.bench("nop_buffer_nonblocking()", async () => {
+ await nop_buffer_nonblocking(buffer);
+});
+
+const { return_bool_nonblocking } = dylib.symbols;
+Deno.bench("return_bool_nonblocking()", async () => {
+ await return_bool_nonblocking();
+});
+
+const { return_u8_nonblocking } = dylib.symbols;
+Deno.bench("return_u8_nonblocking()", async () => {
+ await return_u8_nonblocking();
+});
+
+const { return_i8_nonblocking } = dylib.symbols;
+Deno.bench("return_i8_nonblocking()", async () => {
+ await return_i8_nonblocking();
+});
+
+const { return_u16_nonblocking } = dylib.symbols;
+Deno.bench("return_u16_nonblocking()", async () => {
+ await return_u16_nonblocking();
+});
+
+const { return_i16_nonblocking } = dylib.symbols;
+Deno.bench("return_i16_nonblocking()", async () => {
+ await return_i16_nonblocking();
+});
+
+const { return_u32_nonblocking } = dylib.symbols;
+Deno.bench("return_u32_nonblocking()", async () => {
+ await return_u32_nonblocking();
+});
+
+const { return_i32_nonblocking } = dylib.symbols;
+Deno.bench("return_i32_nonblocking()", async () => {
+ await return_i32_nonblocking();
+});
+
+const { return_u64_nonblocking } = dylib.symbols;
+Deno.bench("return_u64_nonblocking()", async () => {
+ await return_u64_nonblocking();
+});
+
+const { return_i64_nonblocking } = dylib.symbols;
+Deno.bench("return_i64_nonblocking()", async () => {
+ await return_i64_nonblocking();
+});
+
+const { return_usize_nonblocking } = dylib.symbols;
+Deno.bench("return_usize_nonblocking()", async () => {
+ await return_usize_nonblocking();
+});
+
+const { return_isize_nonblocking } = dylib.symbols;
+Deno.bench("return_isize_nonblocking()", async () => {
+ await return_isize_nonblocking();
+});
+
+const { return_f32_nonblocking } = dylib.symbols;
+Deno.bench("return_f32_nonblocking()", async () => {
+ await return_f32_nonblocking();
+});
+
+const { return_f64_nonblocking } = dylib.symbols;
+Deno.bench("return_f64_nonblocking()", async () => {
+ await return_f64_nonblocking();
+});
+
+const { return_buffer_nonblocking } = dylib.symbols;
+Deno.bench("return_buffer_nonblocking()", async () => {
+ await return_buffer_nonblocking();
+});
+
+const { nop_many_parameters } = dylib.symbols;
+const buffer2 = new Uint8Array(8).fill(25);
+Deno.bench("nop_many_parameters()", () => {
+ nop_many_parameters(
+ 135,
+ 47,
+ 356,
+ -236,
+ 7457,
+ -1356,
+ 16471468n,
+ -1334748136n,
+ 132658769535n,
+ -42745856824n,
+ 13567.26437,
+ 7.686234e-3,
+ buffer,
+ 64,
+ -42,
+ 83,
+ -136,
+ 3657,
+ -2376,
+ 3277918n,
+ -474628146n,
+ 344657895n,
+ -2436732n,
+ 135.26437e3,
+ 264.3576468623546834,
+ buffer2,
+ );
+});
+
+const { nop_many_parameters_nonblocking } = dylib.symbols;
+Deno.bench("nop_many_parameters_nonblocking()", () => {
+ nop_many_parameters_nonblocking(
+ 135,
+ 47,
+ 356,
+ -236,
+ 7457,
+ -1356,
+ 16471468n,
+ -1334748136n,
+ 132658769535n,
+ -42745856824n,
+ 13567.26437,
+ 7.686234e-3,
+ buffer,
+ 64,
+ -42,
+ 83,
+ -136,
+ 3657,
+ -2376,
+ 3277918n,
+ -474628146n,
+ 344657895n,
+ -2436732n,
+ 135.26437e3,
+ 264.3576468623546834,
+ buffer2,
+ );
+});
+
+Deno.bench("Deno.UnsafePointer.of", () => {
+ Deno.UnsafePointer.of(buffer);
+});
+
+const cstringBuffer = new TextEncoder().encode("Best believe it!\0");
+const cstringPointerView = new Deno.UnsafePointerView(
+ Deno.UnsafePointer.of(cstringBuffer),
+);
+Deno.bench("Deno.UnsafePointerView#getCString", () => {
+ cstringPointerView.getCString();
+});
+
+const bufferPointerView = new Deno.UnsafePointerView(
+ Deno.UnsafePointer.of(buffer),
+);
+
+Deno.bench("Deno.UnsafePointerView#getBool", () => {
+ bufferPointerView.getBool();
+});
+
+Deno.bench("Deno.UnsafePointerView#getUint8", () => {
+ bufferPointerView.getUint8();
+});
+
+Deno.bench("Deno.UnsafePointerView#getInt8", () => {
+ bufferPointerView.getInt8();
+});
+
+Deno.bench("Deno.UnsafePointerView#getUint16", () => {
+ bufferPointerView.getUint16();
+});
+
+Deno.bench("Deno.UnsafePointerView#getInt16", () => {
+ bufferPointerView.getInt16();
+});
+
+Deno.bench("Deno.UnsafePointerView#getUint32", () => {
+ bufferPointerView.getUint32();
+});
+
+Deno.bench("Deno.UnsafePointerView#getInt32", () => {
+ bufferPointerView.getInt32();
+});
+
+Deno.bench("Deno.UnsafePointerView#getBigUint64", () => {
+ bufferPointerView.getBigUint64();
+});
+
+Deno.bench("Deno.UnsafePointerView#getBigInt64", () => {
+ bufferPointerView.getBigInt64();
+});
+
+Deno.bench("Deno.UnsafePointerView#getFloat32", () => {
+ bufferPointerView.getFloat32();
+});
+
+Deno.bench("Deno.UnsafePointerView#getFloat64", () => {
+ bufferPointerView.getFloat64();
+});
diff --git a/tests/ffi/tests/event_loop_integration.ts b/tests/ffi/tests/event_loop_integration.ts
new file mode 100644
index 000000000..d9ada6027
--- /dev/null
+++ b/tests/ffi/tests/event_loop_integration.ts
@@ -0,0 +1,78 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(
+ libPath,
+ {
+ store_function: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function: {
+ parameters: [],
+ result: "void",
+ },
+ call_stored_function_thread_safe_and_log: {
+ parameters: [],
+ result: "void",
+ },
+ } as const,
+);
+
+let retry = false;
+const tripleLogCallback = () => {
+ console.log("Sync");
+ queueMicrotask(() => {
+ console.log("Async");
+ callback.unref();
+ });
+ setTimeout(() => {
+ console.log("Timeout");
+ callback.unref();
+
+ if (retry) {
+ // Re-ref and retry the call to make sure re-refing works.
+ console.log("RETRY THREAD SAFE");
+ retry = false;
+ callback.ref();
+ dylib.symbols.call_stored_function_thread_safe_and_log();
+ }
+ }, 100);
+};
+
+const callback = Deno.UnsafeCallback.threadSafe(
+ {
+ parameters: [],
+ result: "void",
+ } as const,
+ tripleLogCallback,
+);
+
+// Store function
+dylib.symbols.store_function(callback.pointer);
+
+// Synchronous callback logging
+console.log("SYNCHRONOUS");
+// This function only calls the callback, and does not log.
+dylib.symbols.call_stored_function();
+console.log("STORED_FUNCTION called");
+
+// Wait to make sure synch logging and async logging
+await new Promise((res) => setTimeout(res, 200));
+
+// Ref once to make sure both `queueMicrotask()` and `setTimeout()`
+// must resolve and unref before isolate exists.
+// One ref'ing has been done by `threadSafe` constructor.
+callback.ref();
+
+console.log("THREAD SAFE");
+retry = true;
+// This function calls the callback and logs 'STORED_FUNCTION called'
+dylib.symbols.call_stored_function_thread_safe_and_log();
diff --git a/tests/ffi/tests/ffi_callback_errors.ts b/tests/ffi/tests/ffi_callback_errors.ts
new file mode 100644
index 000000000..dbd60636c
--- /dev/null
+++ b/tests/ffi/tests/ffi_callback_errors.ts
@@ -0,0 +1,141 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(
+ libPath,
+ {
+ store_function_2: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function_2: {
+ parameters: ["u8"],
+ result: "void",
+ },
+ call_stored_function_2_from_other_thread: {
+ name: "call_stored_function_2",
+ parameters: ["u8"],
+ result: "void",
+ nonblocking: true,
+ },
+ call_stored_function_2_thread_safe: {
+ parameters: ["u8"],
+ result: "void",
+ },
+ } as const,
+);
+
+globalThis.addEventListener("error", (data) => {
+ console.log("Unhandled error");
+ data.preventDefault();
+});
+globalThis.onerror = (data) => {
+ console.log("Unhandled error");
+ if (typeof data !== "string") {
+ data.preventDefault();
+ }
+};
+
+globalThis.addEventListener("unhandledrejection", (data) => {
+ console.log("Unhandled rejection");
+ data.preventDefault();
+});
+
+const timer = setTimeout(() => {
+ console.error(
+ "Test failed, final callback did not get picked up by Deno event loop",
+ );
+ Deno.exit(-1);
+}, 5_000);
+
+Deno.unrefTimer(timer);
+
+enum CallCase {
+ SyncSelf,
+ SyncFfi,
+ AsyncSelf,
+ AsyncSyncFfi,
+ AsyncFfi,
+}
+type U8CallCase = Deno.NativeU8Enum<CallCase>;
+
+const throwCb = (c: CallCase): number => {
+ console.log("CallCase:", CallCase[c]);
+ if (c === CallCase.AsyncFfi) {
+ cb.unref();
+ }
+ throw new Error("Error");
+};
+
+const THROW_CB_DEFINITION = {
+ parameters: ["u8" as U8CallCase],
+ result: "u8",
+} as const;
+
+const cb = new Deno.UnsafeCallback(THROW_CB_DEFINITION, throwCb);
+
+try {
+ const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, THROW_CB_DEFINITION);
+
+ fnPointer.call(CallCase.SyncSelf);
+} catch (_err) {
+ console.log(
+ "Throwing errors from an UnsafeCallback called from a synchronous UnsafeFnPointer works. Terribly excellent.",
+ );
+}
+
+dylib.symbols.store_function_2(cb.pointer);
+try {
+ dylib.symbols.call_stored_function_2(CallCase.SyncFfi);
+} catch (_err) {
+ console.log(
+ "Throwing errors from an UnsafeCallback called from a synchronous FFI symbol works. Terribly excellent.",
+ );
+}
+
+try {
+ const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, {
+ ...THROW_CB_DEFINITION,
+ nonblocking: true,
+ });
+ await fnPointer.call(CallCase.AsyncSelf);
+} catch (err) {
+ throw new Error(
+ "Nonblocking UnsafeFnPointer should not be threading through a JS error thrown on the other side of the call",
+ {
+ cause: err,
+ },
+ );
+}
+
+try {
+ await dylib.symbols.call_stored_function_2_from_other_thread(
+ CallCase.AsyncSyncFfi,
+ );
+} catch (err) {
+ throw new Error(
+ "Nonblocking symbol call should not be threading through a JS error thrown on the other side of the call",
+ {
+ cause: err,
+ },
+ );
+}
+try {
+ // Ref the callback to make sure we do not exit before the call is done.
+ cb.ref();
+ dylib.symbols.call_stored_function_2_thread_safe(CallCase.AsyncFfi);
+} catch (err) {
+ throw new Error(
+ "Blocking symbol call should not be travelling 1.5 seconds forward in time to figure out that it call will trigger a JS error to be thrown",
+ {
+ cause: err,
+ },
+ );
+}
diff --git a/tests/ffi/tests/ffi_types.ts b/tests/ffi/tests/ffi_types.ts
new file mode 100644
index 000000000..596662873
--- /dev/null
+++ b/tests/ffi/tests/ffi_types.ts
@@ -0,0 +1,529 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+// Only for testing types. Invoke with `deno cache`
+
+const remote = Deno.dlopen(
+ "dummy_lib.so",
+ {
+ method1: { parameters: ["usize", "bool"], result: "void", callback: true },
+ method2: { parameters: [], result: "void" },
+ method3: { parameters: ["usize"], result: "void" },
+ method4: { parameters: ["isize"], result: "void" },
+ method5: { parameters: ["u8"], result: "void" },
+ method6: { parameters: ["u16"], result: "void" },
+ method7: { parameters: ["u32"], result: "void" },
+ method8: { parameters: ["u64"], result: "void" },
+ method9: { parameters: ["i8"], result: "void" },
+ method10: { parameters: ["i16"], result: "void" },
+ method11: { parameters: ["i32"], result: "void" },
+ method12: { parameters: ["i64"], result: "void" },
+ method13: { parameters: ["f32"], result: "void" },
+ method14: { parameters: ["f64"], result: "void" },
+ method15: { parameters: ["pointer"], result: "void" },
+ method16: { parameters: [], result: "usize" },
+ method17: { parameters: [], result: "usize", nonblocking: true },
+ method18: { parameters: [], result: "pointer" },
+ method19: { parameters: [], result: "pointer", nonblocking: true },
+ method20: {
+ parameters: ["pointer"],
+ result: "void",
+ },
+ method21: {
+ parameters: [
+ "pointer",
+ ],
+ result: "void",
+ },
+ method22: {
+ parameters: ["pointer"],
+ result: "void",
+ },
+ method23: {
+ parameters: ["buffer"],
+ result: "void",
+ },
+ method24: {
+ parameters: ["bool"],
+ result: "bool",
+ },
+ method25: {
+ parameters: [],
+ result: "void",
+ optional: true,
+ },
+ static1: { type: "usize" },
+ static2: { type: "pointer" },
+ static3: { type: "usize" },
+ static4: { type: "isize" },
+ static5: { type: "u8" },
+ static6: { type: "u16" },
+ static7: { type: "u32" },
+ static8: { type: "u64" },
+ static9: { type: "i8" },
+ static10: { type: "i16" },
+ static11: { type: "i32" },
+ static12: { type: "i64" },
+ static13: { type: "f32" },
+ static14: { type: "f64" },
+ static15: { type: "bool" },
+ static16: {
+ type: "bool",
+ optional: true,
+ },
+ },
+);
+
+Deno.dlopen(
+ "dummy_lib_2.so",
+ {
+ wrong_method1: {
+ parameters: [],
+ result: "function",
+ },
+ },
+);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method1(0);
+// @ts-expect-error: Invalid argument
+remote.symbols.method1(0, 0);
+// @ts-expect-error: Invalid argument
+remote.symbols.method1(true, true);
+// @ts-expect-error: Invalid return type
+<number> remote.symbols.method1(0, true);
+<void> remote.symbols.method1(0n, true);
+
+// @ts-expect-error: Expected 0 arguments, but got 1.
+remote.symbols.method2(null);
+remote.symbols.method2();
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method3(null);
+remote.symbols.method3(0n);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method4(null);
+remote.symbols.method4(0n);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method5(null);
+remote.symbols.method5(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method6(null);
+remote.symbols.method6(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method7(null);
+remote.symbols.method7(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method8(null);
+remote.symbols.method8(0n);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method9(null);
+remote.symbols.method9(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method10(null);
+remote.symbols.method10(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method11(null);
+remote.symbols.method11(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method12(null);
+remote.symbols.method12(0n);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method13(null);
+remote.symbols.method13(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method14(null);
+remote.symbols.method14(0);
+
+// @ts-expect-error: Invalid argument
+remote.symbols.method15("foo");
+// @ts-expect-error: Invalid argument
+remote.symbols.method15(new Uint16Array(1));
+remote.symbols.method15(null);
+remote.symbols.method15({} as Deno.PointerValue);
+
+const result = remote.symbols.method16();
+// @ts-expect-error: Invalid argument
+let r_0: string = result;
+let r_1: number | bigint = result;
+
+const result2 = remote.symbols.method17();
+// @ts-expect-error: Invalid argument
+result2.then((_0: string) => {});
+result2.then((_1: number | bigint) => {});
+
+const result3 = remote.symbols.method18();
+// @ts-expect-error: Invalid argument
+let r3_0: Deno.BufferSource = result3;
+let r3_1: null | Deno.UnsafePointer = result3;
+
+const result4 = remote.symbols.method19();
+// @ts-expect-error: Invalid argument
+result4.then((_0: Deno.BufferSource) => {});
+result4.then((_1: null | Deno.UnsafePointer) => {});
+
+const fnptr = new Deno.UnsafeFnPointer(
+ {} as Deno.PointerObject,
+ {
+ parameters: ["u32", "pointer"],
+ result: "void",
+ },
+);
+// @ts-expect-error: Invalid argument
+fnptr.call(null, null);
+fnptr.call(0, null);
+
+const unsafe_callback_wrong1 = new Deno.UnsafeCallback(
+ {
+ parameters: ["i8"],
+ result: "void",
+ },
+ // @ts-expect-error: i8 is not a pointer
+ (_: bigint) => {},
+);
+const unsafe_callback_wrong2 = new Deno.UnsafeCallback(
+ {
+ parameters: ["pointer"],
+ result: "u64",
+ },
+ // @ts-expect-error: must return a number or bigint
+ (_: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_wrong3 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: "void",
+ },
+ // @ts-expect-error: no parameters
+ (_: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_wrong4 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u64"],
+ result: "void",
+ },
+ // @ts-expect-error: Callback's 64bit parameters are either number or bigint
+ (_: number) => {},
+);
+const unsafe_callback_right1 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "u32", "pointer"],
+ result: "void",
+ },
+ (_1: number, _2: number, _3: null | Deno.PointerValue) => {},
+);
+const unsafe_callback_right2 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: "u8",
+ },
+ () => 3,
+);
+const unsafe_callback_right3 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: "function",
+ },
+ // Callbacks can return other callbacks' pointers, if really wanted.
+ () => unsafe_callback_right2.pointer,
+);
+const unsafe_callback_right4 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "u32", "pointer"],
+ result: "u8",
+ },
+ (_1: number, _2: number, _3: null | Deno.PointerValue) => 3,
+);
+const unsafe_callback_right5 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "i32", "pointer"],
+ result: "void",
+ },
+ (_1: number, _2: number, _3: null | Deno.PointerValue) => {},
+);
+
+// @ts-expect-error: Must pass callback
+remote.symbols.method20();
+// nullptr is okay
+remote.symbols.method20(null);
+// @ts-expect-error: Callback cannot be passed directly
+remote.symbols.method20(unsafe_callback_right2);
+remote.symbols.method20(unsafe_callback_right1.pointer);
+
+remote.symbols.method23(new Uint8Array(1));
+remote.symbols.method23(new Uint32Array(1));
+remote.symbols.method23(new Uint8Array(1));
+
+// @ts-expect-error: Cannot pass pointer values as buffer.
+remote.symbols.method23({});
+// @ts-expect-error: Cannot pass pointer values as buffer.
+remote.symbols.method23({});
+remote.symbols.method23(null);
+
+// @ts-expect-error: Cannot pass number as bool.
+remote.symbols.method24(0);
+// @ts-expect-error: Cannot pass number as bool.
+remote.symbols.method24(1);
+// @ts-expect-error: Cannot pass null as bool.
+remote.symbols.method24(null);
+remote.symbols.method24(true);
+remote.symbols.method24(false);
+// @ts-expect-error: Cannot assert return type as a number.
+<number> remote.symbols.method24(true);
+// @ts-expect-error: Cannot assert return type truthiness.
+let r24_0: true = remote.symbols.method24(true);
+// @ts-expect-error: Cannot assert return type as a number.
+let r42_1: number = remote.symbols.method24(true);
+<boolean> remote.symbols.method24(Math.random() > 0.5);
+
+// @ts-expect-error: Optional symbol; can be null.
+remote.symbols.method25();
+
+// @ts-expect-error: Invalid member type
+const static1_wrong: null = remote.symbols.static1;
+const static1_right: number | bigint = remote.symbols.static1;
+// @ts-expect-error: Invalid member type
+const static2_wrong: null = remote.symbols.static2;
+const static2_right: null | Deno.UnsafePointer = remote.symbols.static2;
+// @ts-expect-error: Invalid member type
+const static3_wrong: null = remote.symbols.static3;
+const static3_right: number | bigint = remote.symbols.static3;
+// @ts-expect-error: Invalid member type
+const static4_wrong: null = remote.symbols.static4;
+const static4_right: number | bigint = remote.symbols.static4;
+// @ts-expect-error: Invalid member type
+const static5_wrong: null = remote.symbols.static5;
+const static5_right: number = remote.symbols.static5;
+// @ts-expect-error: Invalid member type
+const static6_wrong: null = remote.symbols.static6;
+const static6_right: number = remote.symbols.static6;
+// @ts-expect-error: Invalid member type
+const static7_wrong: null = remote.symbols.static7;
+const static7_right: number = remote.symbols.static7;
+// @ts-expect-error: Invalid member type
+const static8_wrong: null = remote.symbols.static8;
+const static8_right: number | bigint = remote.symbols.static8;
+// @ts-expect-error: Invalid member type
+const static9_wrong: null = remote.symbols.static9;
+const static9_right: number = remote.symbols.static9;
+// @ts-expect-error: Invalid member type
+const static10_wrong: null = remote.symbols.static10;
+const static10_right: number = remote.symbols.static10;
+// @ts-expect-error: Invalid member type
+const static11_wrong: null = remote.symbols.static11;
+const static11_right: number = remote.symbols.static11;
+// @ts-expect-error: Invalid member type
+const static12_wrong: null = remote.symbols.static12;
+const static12_right: number | bigint = remote.symbols.static12;
+// @ts-expect-error: Invalid member type
+const static13_wrong: null = remote.symbols.static13;
+const static13_right: number = remote.symbols.static13;
+// @ts-expect-error: Invalid member type
+const static14_wrong: null = remote.symbols.static14;
+const static14_right: number = remote.symbols.static14;
+// @ts-expect-error: Invalid member type
+const static15_wrong: number = remote.symbols.static15;
+const static15_right: boolean = remote.symbols.static15;
+// @ts-expect-error: Invalid member type
+const static16_wrong: boolean = remote.symbols.static16;
+const static16_right: boolean | null = remote.symbols.static16;
+
+// Adapted from https://stackoverflow.com/a/53808212/10873797
+type Equal<T, U> = (<G>() => G extends T ? 1 : 2) extends
+ (<G>() => G extends U ? 1 : 2) ? true
+ : false;
+
+type AssertEqual<
+ Expected extends $,
+ Got extends $$,
+ $ = [Equal<Got, Expected>] extends [true] ? Expected
+ : ([Expected] extends [Got] ? never : Got),
+ $$ = [Equal<Expected, Got>] extends [true] ? Got
+ : ([Got] extends [Expected] ? never : Got),
+> = never;
+
+type AssertNotEqual<
+ Expected extends $,
+ Got,
+ $ = [Equal<Expected, Got>] extends [true] ? never : Expected,
+> = never;
+
+const enum FooEnum {
+ Foo,
+ Bar,
+}
+const foo = "u8" as Deno.NativeU8Enum<FooEnum>;
+
+declare const brand: unique symbol;
+class MyPointerClass {}
+type MyPointer = Deno.PointerObject & { [brand]: MyPointerClass };
+const myPointer = "pointer" as Deno.NativeTypedPointer<MyPointer>;
+type MyFunctionDefinition = Deno.UnsafeCallbackDefinition<
+ [typeof foo, "u32"],
+ typeof myPointer
+>;
+const myFunction = "function" as Deno.NativeTypedFunction<
+ MyFunctionDefinition
+>;
+
+type __Tests__ = [
+ empty: AssertEqual<
+ { symbols: Record<never, never>; close(): void },
+ Deno.DynamicLibrary<Record<never, never>>
+ >,
+ basic: AssertEqual<
+ { symbols: { add: (n1: number, n2: number) => number }; close(): void },
+ Deno.DynamicLibrary<{ add: { parameters: ["i32", "u8"]; result: "i32" } }>
+ >,
+ higher_order_params: AssertEqual<
+ {
+ symbols: {
+ pushBuf: (
+ buf: BufferSource | null,
+ ptr: Deno.PointerValue | null,
+ func: Deno.PointerValue | null,
+ ) => void;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ {
+ pushBuf: {
+ parameters: ["buffer", "pointer", "function"];
+ result: "void";
+ };
+ }
+ >
+ >,
+ higher_order_returns: AssertEqual<
+ {
+ symbols: {
+ pushBuf: (
+ buf: BufferSource | null,
+ ptr: Deno.PointerValue,
+ func: Deno.PointerValue,
+ ) => Deno.PointerValue;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ {
+ pushBuf: {
+ parameters: ["buffer", "pointer", "function"];
+ result: "pointer";
+ };
+ }
+ >
+ >,
+ non_exact_params: AssertEqual<
+ {
+ symbols: {
+ foo: (
+ ...args: (number | Deno.PointerValue | null)[]
+ ) => number | bigint;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: ("i32" | "pointer")[]; result: "u64" } }
+ >
+ >,
+ non_exact_params_empty: AssertEqual<
+ {
+ symbols: {
+ foo: () => number;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: []; result: "i32" } }
+ >
+ >,
+ non_exact_params_empty: AssertNotEqual<
+ {
+ symbols: {
+ foo: (a: number) => number;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: []; result: "i32" } }
+ >
+ >,
+ enum_param: AssertEqual<
+ {
+ symbols: {
+ foo: (arg: FooEnum) => void;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: [typeof foo]; result: "void" } }
+ >
+ >,
+ enum_return: AssertEqual<
+ {
+ symbols: {
+ foo: () => FooEnum;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: []; result: typeof foo } }
+ >
+ >,
+ typed_pointer_param: AssertEqual<
+ {
+ symbols: {
+ foo: (arg: MyPointer | null) => void;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: [typeof myPointer]; result: "void" } }
+ >
+ >,
+ typed_pointer_return: AssertEqual<
+ {
+ symbols: {
+ foo: () => MyPointer | null;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: []; result: typeof myPointer } }
+ >
+ >,
+ typed_function_param: AssertEqual<
+ {
+ symbols: {
+ foo: (arg: Deno.PointerObject<MyFunctionDefinition> | null) => void;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: [typeof myFunction]; result: "void" } }
+ >
+ >,
+ typed_function_return: AssertEqual<
+ {
+ symbols: {
+ foo: () => Deno.PointerObject<MyFunctionDefinition> | null;
+ };
+ close(): void;
+ },
+ Deno.DynamicLibrary<
+ { foo: { parameters: []; result: typeof myFunction } }
+ >
+ >,
+];
diff --git a/tests/ffi/tests/integration_tests.rs b/tests/ffi/tests/integration_tests.rs
new file mode 100644
index 000000000..0ad95254c
--- /dev/null
+++ b/tests/ffi/tests/integration_tests.rs
@@ -0,0 +1,302 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use pretty_assertions::assert_eq;
+use std::process::Command;
+use test_util::deno_cmd;
+use test_util::deno_config_path;
+use test_util::ffi_tests_path;
+
+#[cfg(debug_assertions)]
+const BUILD_VARIANT: &str = "debug";
+
+#[cfg(not(debug_assertions))]
+const BUILD_VARIANT: &str = "release";
+
+fn build() {
+ let mut build_plugin_base = Command::new("cargo");
+ let mut build_plugin =
+ build_plugin_base.arg("build").arg("-p").arg("test_ffi");
+ if BUILD_VARIANT == "release" {
+ build_plugin = build_plugin.arg("--release");
+ }
+ let build_plugin_output = build_plugin.output().unwrap();
+ assert!(build_plugin_output.status.success());
+}
+
+#[test]
+fn basic() {
+ build();
+
+ let output = deno_cmd()
+ .current_dir(ffi_tests_path())
+ .arg("run")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg("--allow-ffi")
+ .arg("--allow-read")
+ .arg("--unstable-ffi")
+ .arg("--quiet")
+ .arg(r#"--v8-flags=--allow-natives-syntax"#)
+ .arg("tests/test.js")
+ .env("NO_COLOR", "1")
+ .output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ if !output.status.success() {
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ println!("{:?}", output.status);
+ assert!(output.status.success());
+ let expected = "\
+ something\n\
+ [1, 2, 3, 4, 5, 6, 7, 8]\n\
+ [4, 5, 6]\n\
+ [1, 2, 3, 4, 5, 6, 7, 8] [9, 10]\n\
+ [1, 2, 3, 4, 5, 6, 7, 8]\n\
+ [ 1, 2, 3, 4, 5, 6 ]\n\
+ [ 4, 5, 6 ]\n\
+ [ 4, 5, 6 ]\n\
+ Hello from pointer!\n\
+ pointer!\n\
+ false false\n\
+ true true\n\
+ false false\n\
+ true true\n\
+ false false\n\
+ 579\n\
+ true\n\
+ 579\n\
+ 579\n\
+ 5\n\
+ 5\n\
+ 579\n\
+ 8589934590\n\
+ -8589934590\n\
+ 8589934590\n\
+ -8589934590\n\
+ 9007199254740992n\n\
+ 9007199254740992n\n\
+ -9007199254740992n\n\
+ 9007199254740992n\n\
+ 9007199254740992n\n\
+ -9007199254740992n\n\
+ 579.9119873046875\n\
+ 579.912\n\
+ true\n\
+ false\n\
+ 579.9119873046875\n\
+ 579.9119873046875\n\
+ 579.912\n\
+ 579.912\n\
+ 579\n\
+ 8589934590\n\
+ -8589934590\n\
+ 8589934590\n\
+ -8589934590\n\
+ 9007199254740992n\n\
+ 9007199254740992n\n\
+ -9007199254740992n\n\
+ 9007199254740992n\n\
+ 9007199254740992n\n\
+ -9007199254740992n\n\
+ 579.9119873046875\n\
+ 579.912\n\
+ Before\n\
+ After\n\
+ logCallback\n\
+ 1 -1 2 -2 3 -3 4 -4 0.5 -0.5 1 2 3 4 5 6 7 8\n\
+ u8: 8\n\
+ buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
+ logCallback\n\
+ 30\n\
+ 255 65535 4294967295 4294967296 123.456 789.876 -1 -2 -3 -4 -1000 1000 12345.67891 12345.679 12345.67891 12345.679 12345.67891 12345.679 12345.67891\n\
+ 255 65535 4294967295 4294967296 123.456 789.876 -1 -2 -3 -4 -1000 1000 12345.67891 12345.679 12345.67891 12345.679 12345.67891 12345.679 12345.67891\n\
+ 0\n\
+ 0\n\
+ 0\n\
+ 0\n\
+ 78\n\
+ 78\n\
+ STORED_FUNCTION cleared\n\
+ STORED_FUNCTION_2 cleared\n\
+ logCallback\n\
+ u8: 8\n\
+ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\
+ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\
+ Rect { x: 20.0, y: 20.0, w: 100.0, h: 200.0 }\n\
+ Mixed { u8: 3, f32: 12.515, rect: Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }, usize: 12456789, array: [8, 32] }\n\
+ 2264956937\n\
+ 2264956937\n\
+ Correct number of resources\n";
+ assert_eq!(stdout, expected);
+ assert_eq!(stderr, "");
+}
+
+#[test]
+fn symbol_types() {
+ build();
+
+ let output = deno_cmd()
+ .current_dir(ffi_tests_path())
+ .arg("check")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg("--unstable-ffi")
+ .arg("--quiet")
+ .arg("tests/ffi_types.ts")
+ .env("NO_COLOR", "1")
+ .output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ if !output.status.success() {
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ println!("{:?}", output.status);
+ assert!(output.status.success());
+ assert_eq!(stderr, "");
+}
+
+#[test]
+fn thread_safe_callback() {
+ build();
+
+ let output = deno_cmd()
+ .current_dir(ffi_tests_path())
+ .arg("run")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg("--allow-ffi")
+ .arg("--allow-read")
+ .arg("--unstable-ffi")
+ .arg("--quiet")
+ .arg("tests/thread_safe_test.js")
+ .env("NO_COLOR", "1")
+ .output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ if !output.status.success() {
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ println!("{:?}", output.status);
+ assert!(output.status.success());
+ let expected = "\
+ Callback on main thread\n\
+ Callback on worker thread\n\
+ STORED_FUNCTION cleared\n\
+ Calling callback, isolate should stay asleep until callback is called\n\
+ Callback being called\n\
+ STORED_FUNCTION cleared\n\
+ Isolate should now exit\n";
+ assert_eq!(stdout, expected);
+ assert_eq!(stderr, "");
+}
+
+#[test]
+fn event_loop_integration() {
+ build();
+
+ let output = deno_cmd()
+ .current_dir(ffi_tests_path())
+ .arg("run")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg("--allow-ffi")
+ .arg("--allow-read")
+ .arg("--unstable-ffi")
+ .arg("--quiet")
+ .arg("tests/event_loop_integration.ts")
+ .env("NO_COLOR", "1")
+ .output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ if !output.status.success() {
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ println!("{:?}", output.status);
+ assert!(output.status.success());
+ // TODO(aapoalas): The order of logging in thread safe callbacks is
+ // unexpected: The callback logs synchronously and creates an asynchronous
+ // logging task, which then gets called synchronously before the callback
+ // actually yields to the calling thread. This is in contrast to what the
+ // logging would look like if the call was coming from within Deno itself,
+ // and may lead users to unknowingly run heavy asynchronous tasks from thread
+ // safe callbacks synchronously.
+ // The fix would be to make sure microtasks are only run after the event loop
+ // middleware that polls them has completed its work. This just does not seem
+ // to work properly with Linux release builds.
+ let expected = "\
+ SYNCHRONOUS\n\
+ Sync\n\
+ STORED_FUNCTION called\n\
+ Async\n\
+ Timeout\n\
+ THREAD SAFE\n\
+ Sync\n\
+ Async\n\
+ STORED_FUNCTION called\n\
+ Timeout\n\
+ RETRY THREAD SAFE\n\
+ Sync\n\
+ Async\n\
+ STORED_FUNCTION called\n\
+ Timeout\n";
+ assert_eq!(stdout, expected);
+ assert_eq!(stderr, "");
+}
+
+#[test]
+fn ffi_callback_errors_test() {
+ build();
+
+ let output = deno_cmd()
+ .current_dir(ffi_tests_path())
+ .arg("run")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg("--allow-ffi")
+ .arg("--allow-read")
+ .arg("--unstable-ffi")
+ .arg("--quiet")
+ .arg("tests/ffi_callback_errors.ts")
+ .env("NO_COLOR", "1")
+ .output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ if !output.status.success() {
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ println!("{:?}", output.status);
+ assert!(output.status.success());
+
+ let expected = "\
+ CallCase: SyncSelf\n\
+ Throwing errors from an UnsafeCallback called from a synchronous UnsafeFnPointer works. Terribly excellent.\n\
+ CallCase: SyncFfi\n\
+ 0\n\
+ Throwing errors from an UnsafeCallback called from a synchronous FFI symbol works. Terribly excellent.\n\
+ CallCase: AsyncSelf\n\
+ CallCase: AsyncSyncFfi\n\
+ 0\n\
+ Calling\n\
+ CallCase: AsyncFfi\n";
+ assert_eq!(stdout, expected);
+ assert_eq!(
+ stderr,
+ "Illegal unhandled exception in nonblocking callback.\n".repeat(3)
+ );
+}
diff --git a/tests/ffi/tests/test.js b/tests/ffi/tests/test.js
new file mode 100644
index 000000000..6b8e509c0
--- /dev/null
+++ b/tests/ffi/tests/test.js
@@ -0,0 +1,802 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+
+// Run using cargo test or `--v8-flags=--allow-natives-syntax`
+
+import {
+ assertThrows,
+ assert,
+ assertNotEquals,
+ assertInstanceOf,
+ assertEquals,
+ assertFalse,
+} from "@std/assert/mod.ts";
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const resourcesPre = Deno[Deno.internal].core.resources();
+
+// dlopen shouldn't panic
+assertThrows(() => {
+ Deno.dlopen("cli/src/main.rs", {});
+});
+
+assertThrows(
+ () => {
+ Deno.dlopen(libPath, {
+ non_existent_symbol: {
+ parameters: [],
+ result: "void",
+ },
+ });
+ },
+ Error,
+ "Failed to register symbol non_existent_symbol",
+);
+
+assertThrows(() => {
+ Deno.dlopen(libPath, {
+ print_something: {
+ parameters: [],
+ result: { struct: [] }
+ },
+ }),
+ TypeError,
+ "Struct must have at least one field"
+});
+
+assertThrows(() => {
+ Deno.dlopen(libPath, {
+ print_something: {
+ parameters: [ { struct: [] } ],
+ result: "void",
+ },
+ }),
+ TypeError,
+ "Struct must have at least one field"
+});
+
+const Empty = { struct: [] }
+assertThrows(() => {
+ Deno.dlopen(libPath, {
+ print_something: {
+ parameters: [ { struct: [Empty] } ],
+ result: "void",
+ },
+ }),
+ TypeError,
+ "Struct must have at least one field"
+});
+
+const Point = ["f64", "f64"];
+const Size = ["f64", "f64"];
+const Rect = ["f64", "f64", "f64", "f64"];
+const RectNested = [{ struct: Point }, { struct: Size }];
+const RectNestedCached = [{ struct: Size }, { struct: Size }];
+const Mixed = ["u8", "f32", { struct: Rect }, "usize", { struct: ["u32", "u32"] }];
+
+const dylib = Deno.dlopen(libPath, {
+ "printSomething": {
+ name: "print_something",
+ parameters: [],
+ result: "void",
+ },
+ "print_buffer": { parameters: ["buffer", "usize"], result: "void" },
+ "print_pointer": { name: "print_buffer", parameters: ["pointer", "usize"], result: "void" },
+ "print_buffer2": {
+ parameters: ["buffer", "usize", "buffer", "usize"],
+ result: "void",
+ },
+ "return_buffer": { parameters: [], result: "buffer" },
+ "is_null_ptr": { parameters: ["pointer"], result: "bool" },
+ "is_null_buf": { name: "is_null_ptr", parameters: ["buffer"], result: "bool" },
+ "add_u32": { parameters: ["u32", "u32"], result: "u32" },
+ "add_i32": { parameters: ["i32", "i32"], result: "i32" },
+ "add_u64": { parameters: ["u64", "u64"], result: "u64" },
+ "add_i64": { parameters: ["i64", "i64"], result: "i64" },
+ "add_usize": { parameters: ["usize", "usize"], result: "usize" },
+ "add_usize_fast": { parameters: ["usize", "usize"], result: "u32" },
+ "add_isize": { parameters: ["isize", "isize"], result: "isize" },
+ "add_f32": { parameters: ["f32", "f32"], result: "f32" },
+ "add_f64": { parameters: ["f64", "f64"], result: "f64" },
+ "and": { parameters: ["bool", "bool"], result: "bool" },
+ "add_u32_nonblocking": {
+ name: "add_u32",
+ parameters: ["u32", "u32"],
+ result: "u32",
+ nonblocking: true,
+ },
+ "add_i32_nonblocking": {
+ name: "add_i32",
+ parameters: ["i32", "i32"],
+ result: "i32",
+ nonblocking: true,
+ },
+ "add_u64_nonblocking": {
+ name: "add_u64",
+ parameters: ["u64", "u64"],
+ result: "u64",
+ nonblocking: true,
+ },
+ "add_i64_nonblocking": {
+ name: "add_i64",
+ parameters: ["i64", "i64"],
+ result: "i64",
+ nonblocking: true,
+ },
+ "add_usize_nonblocking": {
+ name: "add_usize",
+ parameters: ["usize", "usize"],
+ result: "usize",
+ nonblocking: true,
+ },
+ "add_isize_nonblocking": {
+ name: "add_isize",
+ parameters: ["isize", "isize"],
+ result: "isize",
+ nonblocking: true,
+ },
+ "add_f32_nonblocking": {
+ name: "add_f32",
+ parameters: ["f32", "f32"],
+ result: "f32",
+ nonblocking: true,
+ },
+ "add_f64_nonblocking": {
+ name: "add_f64",
+ parameters: ["f64", "f64"],
+ result: "f64",
+ nonblocking: true,
+ },
+ "fill_buffer": { parameters: ["u8", "buffer", "usize"], result: "void" },
+ "sleep_nonblocking": {
+ name: "sleep_blocking",
+ parameters: ["u64"],
+ result: "void",
+ nonblocking: true,
+ },
+ "sleep_blocking": { parameters: ["u64"], result: "void" },
+ "nonblocking_buffer": {
+ parameters: ["buffer", "usize"],
+ result: "void",
+ nonblocking: true,
+ },
+ "get_add_u32_ptr": {
+ parameters: [],
+ result: "pointer",
+ },
+ "get_sleep_blocking_ptr": {
+ parameters: [],
+ result: "pointer",
+ },
+ // Callback function
+ call_fn_ptr: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_fn_ptr_thread_safe: {
+ name: "call_fn_ptr",
+ parameters: ["function"],
+ result: "void",
+ nonblocking: true,
+ },
+ call_fn_ptr_many_parameters: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_fn_ptr_return_u8: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_fn_ptr_return_u8_thread_safe: {
+ name: "call_fn_ptr_return_u8",
+ parameters: ["function"],
+ result: "void",
+ },
+ call_fn_ptr_return_buffer: {
+ parameters: ["function"],
+ result: "void",
+ },
+ store_function: {
+ parameters: ["function"],
+ result: "void",
+ },
+ store_function_2: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function: {
+ parameters: [],
+ result: "void",
+ callback: true,
+ },
+ call_stored_function_2: {
+ parameters: ["u8"],
+ result: "void",
+ callback: true,
+ },
+ log_many_parameters: {
+ parameters: ["u8", "u16", "u32", "u64", "f64", "f32", "i64", "i32", "i16", "i8", "isize", "usize", "f64", "f32", "f64", "f32", "f64", "f32", "f64"],
+ result: "void",
+ },
+ cast_u8_u32: {
+ parameters: ["u8"],
+ result: "u32",
+ },
+ cast_u32_u8: {
+ parameters: ["u32"],
+ result: "u8",
+ },
+ add_many_u16: {
+ parameters: ["u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16"],
+ result: "u16",
+ },
+ // Statics
+ "static_u32": {
+ type: "u32",
+ },
+ "static_i64": {
+ type: "i64",
+ },
+ "static_ptr": {
+ type: "pointer",
+ },
+ /**
+ * Invalid UTF-8 characters, buffer of length 14
+ */
+ "static_char": {
+ type: "pointer",
+ optional: true,
+ },
+ "hash": { parameters: ["buffer", "u32"], result: "u32" },
+ make_rect: {
+ parameters: ["f64", "f64", "f64", "f64"],
+ result: { struct: Rect },
+ },
+ make_rect_async: {
+ name: "make_rect",
+ nonblocking: true,
+ parameters: ["f64", "f64", "f64", "f64"],
+ result: { struct: RectNested },
+ },
+ print_rect: {
+ parameters: [{ struct: RectNestedCached }],
+ result: "void",
+ },
+ print_rect_async: {
+ name: "print_rect",
+ nonblocking: true,
+ parameters: [{ struct: Rect }],
+ result: "void",
+ },
+ create_mixed: {
+ parameters: ["u8", "f32", { struct: Rect }, "pointer", "buffer"],
+ result: { struct: Mixed }
+ },
+ print_mixed: {
+ parameters: [{ struct: Mixed }],
+ result: "void",
+ optional: true,
+ },
+ non_existent_symbol: {
+ parameters: [],
+ result: "void",
+ optional: true,
+ },
+ non_existent_nonblocking_symbol: {
+ parameters: [],
+ result: "void",
+ nonblocking: true,
+ optional: true,
+ },
+ non_existent_static: {
+ type: "u32",
+ optional: true,
+ },
+});
+const { symbols } = dylib;
+
+symbols.printSomething();
+const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+const buffer2 = new Uint8Array([9, 10]);
+dylib.symbols.print_buffer(buffer, buffer.length);
+// Test subarrays
+const subarray = buffer.subarray(3);
+dylib.symbols.print_buffer(subarray, subarray.length - 2);
+dylib.symbols.print_buffer2(buffer, buffer.length, buffer2, buffer2.length);
+
+const { return_buffer } = symbols;
+function returnBuffer() { return return_buffer(); };
+
+%PrepareFunctionForOptimization(returnBuffer);
+returnBuffer();
+%OptimizeFunctionOnNextCall(returnBuffer);
+const ptr0 = returnBuffer();
+assertIsOptimized(returnBuffer);
+
+dylib.symbols.print_pointer(ptr0, 8);
+const ptrView = new Deno.UnsafePointerView(ptr0);
+const into = new Uint8Array(6);
+const into2 = new Uint8Array(3);
+const into2ptr = Deno.UnsafePointer.of(into2);
+const into2ptrView = new Deno.UnsafePointerView(into2ptr);
+const into3 = new Uint8Array(3);
+const into4 = new Uint16Array(3);
+ptrView.copyInto(into4);
+ptrView.copyInto(into);
+console.log([...into]);
+ptrView.copyInto(into2, 3);
+console.log([...into2]);
+into2ptrView.copyInto(into3);
+console.log([...into3]);
+const string = new Uint8Array([
+ ...new TextEncoder().encode("Hello from pointer!"),
+ 0,
+]);
+const stringPtr = Deno.UnsafePointer.of(string);
+const stringPtrview = new Deno.UnsafePointerView(stringPtr);
+console.log(stringPtrview.getCString());
+console.log(stringPtrview.getCString(11));
+console.log("false", dylib.symbols.is_null_ptr(ptr0));
+console.log("true", dylib.symbols.is_null_ptr(null));
+console.log("false", dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into)));
+const emptyBuffer = new Uint8Array(0);
+console.log("true", dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(emptyBuffer)));
+const emptySlice = into.subarray(6);
+console.log("false", dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(emptySlice)));
+
+const { is_null_buf } = symbols;
+function isNullBuffer(buffer) { return is_null_buf(buffer); };
+function isNullBufferDeopt(buffer) { return is_null_buf(buffer); };
+%PrepareFunctionForOptimization(isNullBuffer);
+isNullBuffer(emptyBuffer);
+%NeverOptimizeFunction(isNullBufferDeopt);
+%OptimizeFunctionOnNextCall(isNullBuffer);
+isNullBuffer(emptyBuffer);
+assertIsOptimized(isNullBuffer);
+
+// ==== ZERO LENGTH BUFFER TESTS ====
+assertEquals(isNullBuffer(emptyBuffer), true, "isNullBuffer(emptyBuffer) !== true");
+assertEquals(isNullBufferDeopt(emptyBuffer), true, "isNullBufferDeopt(emptyBuffer) !== true");
+assertEquals(isNullBuffer(emptySlice), false, "isNullBuffer(emptySlice) !== false");
+assertEquals(isNullBufferDeopt(emptySlice), false, "isNullBufferDeopt(emptySlice) !== false");
+assertEquals(isNullBufferDeopt(new Uint8Array()), true, "isNullBufferDeopt(new Uint8Array()) !== true");
+
+// ==== V8 ZERO LENGTH BUFFER ANOMALIES ====
+
+// V8 bug: inline Uint8Array creation to fast call sees non-null pointer
+// https://bugs.chromium.org/p/v8/issues/detail?id=13489
+if (Deno.build.os != "linux" || Deno.build.arch != "aarch64") {
+ assertEquals(isNullBuffer(new Uint8Array()), false, "isNullBuffer(new Uint8Array()) !== false");
+}
+
+// Externally backed ArrayBuffer has a non-null data pointer, even though its length is zero.
+const externalZeroBuffer = new Uint8Array(Deno.UnsafePointerView.getArrayBuffer(ptr0, 0));
+// V8 Fast calls used to get null pointers for all zero-sized buffers no matter their external backing.
+assertEquals(isNullBuffer(externalZeroBuffer), false, "isNullBuffer(externalZeroBuffer) !== false");
+// V8's `Local<ArrayBuffer>->Data()` method also used to similarly return null pointers for all
+// zero-sized buffers which would not match what `Local<ArrayBuffer>->GetBackingStore()->Data()`
+// API returned. These issues have been fixed in https://bugs.chromium.org/p/v8/issues/detail?id=13488.
+assertEquals(isNullBufferDeopt(externalZeroBuffer), false, "isNullBufferDeopt(externalZeroBuffer) !== false");
+
+// The same pointer with a non-zero byte length for the buffer will return non-null pointers in
+// both Fast call and V8 API calls.
+const externalOneBuffer = new Uint8Array(Deno.UnsafePointerView.getArrayBuffer(ptr0, 1));
+assertEquals(isNullBuffer(externalOneBuffer), false, "isNullBuffer(externalOneBuffer) !== false");
+assertEquals(isNullBufferDeopt(externalOneBuffer), false, "isNullBufferDeopt(externalOneBuffer) !== false");
+
+// UnsafePointer.of uses an exact-pointer fallback for zero-length buffers and slices to ensure that it always gets
+// the underlying pointer right.
+assertNotEquals(Deno.UnsafePointer.of(externalZeroBuffer), null, "Deno.UnsafePointer.of(externalZeroBuffer) === null");
+assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), null, "Deno.UnsafePointer.of(externalOneBuffer) === null");
+
+const addU32Ptr = dylib.symbols.get_add_u32_ptr();
+const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, {
+ parameters: ["u32", "u32"],
+ result: "u32",
+});
+console.log(addU32.call(123, 456));
+
+const sleepBlockingPtr = dylib.symbols.get_sleep_blocking_ptr();
+const sleepNonBlocking = new Deno.UnsafeFnPointer(sleepBlockingPtr, {
+ nonblocking: true,
+ parameters: ["u64"],
+ result: "void",
+});
+const before = performance.now();
+await sleepNonBlocking.call(100);
+console.log(performance.now() - before >= 100);
+
+const { add_u32, add_usize_fast } = symbols;
+function addU32Fast(a, b) {
+ return add_u32(a, b);
+};
+testOptimized(addU32Fast, () => addU32Fast(123, 456));
+
+function addU64Fast(a, b) { return add_usize_fast(a, b); };
+testOptimized(addU64Fast, () => addU64Fast(2, 3));
+
+console.log(dylib.symbols.add_i32(123, 456));
+console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn));
+console.log(dylib.symbols.add_i64(-0xffffffffn, -0xffffffffn));
+console.log(dylib.symbols.add_usize(0xffffffffn, 0xffffffffn));
+console.log(dylib.symbols.add_isize(-0xffffffffn, -0xffffffffn));
+console.log(dylib.symbols.add_u64(Number.MAX_SAFE_INTEGER, 1));
+console.log(dylib.symbols.add_i64(Number.MAX_SAFE_INTEGER, 1));
+console.log(dylib.symbols.add_i64(Number.MIN_SAFE_INTEGER, -1));
+console.log(dylib.symbols.add_usize(Number.MAX_SAFE_INTEGER, 1));
+console.log(dylib.symbols.add_isize(Number.MAX_SAFE_INTEGER, 1));
+console.log(dylib.symbols.add_isize(Number.MIN_SAFE_INTEGER, -1));
+console.log(dylib.symbols.add_f32(123.123, 456.789));
+console.log(dylib.symbols.add_f64(123.123, 456.789));
+console.log(dylib.symbols.and(true, true));
+console.log(dylib.symbols.and(true, false));
+
+function addF32Fast(a, b) {
+ return dylib.symbols.add_f32(a, b);
+};
+testOptimized(addF32Fast, () => addF32Fast(123.123, 456.789));
+
+function addF64Fast(a, b) {
+ return dylib.symbols.add_f64(a, b);
+};
+testOptimized(addF64Fast, () => addF64Fast(123.123, 456.789));
+
+// Test adders as nonblocking calls
+console.log(await dylib.symbols.add_i32_nonblocking(123, 456));
+console.log(await dylib.symbols.add_u64_nonblocking(0xffffffffn, 0xffffffffn));
+console.log(
+ await dylib.symbols.add_i64_nonblocking(-0xffffffffn, -0xffffffffn),
+);
+console.log(
+ await dylib.symbols.add_usize_nonblocking(0xffffffffn, 0xffffffffn),
+);
+console.log(
+ await dylib.symbols.add_isize_nonblocking(-0xffffffffn, -0xffffffffn),
+);
+console.log(await dylib.symbols.add_u64_nonblocking(Number.MAX_SAFE_INTEGER, 1));
+console.log(await dylib.symbols.add_i64_nonblocking(Number.MAX_SAFE_INTEGER, 1));
+console.log(await dylib.symbols.add_i64_nonblocking(Number.MIN_SAFE_INTEGER, -1));
+console.log(await dylib.symbols.add_usize_nonblocking(Number.MAX_SAFE_INTEGER, 1));
+console.log(await dylib.symbols.add_isize_nonblocking(Number.MAX_SAFE_INTEGER, 1));
+console.log(await dylib.symbols.add_isize_nonblocking(Number.MIN_SAFE_INTEGER, -1));
+console.log(await dylib.symbols.add_f32_nonblocking(123.123, 456.789));
+console.log(await dylib.symbols.add_f64_nonblocking(123.123, 456.789));
+
+// test mutating sync calls
+
+function test_fill_buffer(fillValue, arr) {
+ let buf = new Uint8Array(arr);
+ dylib.symbols.fill_buffer(fillValue, buf, buf.length);
+ for (let i = 0; i < buf.length; i++) {
+ if (buf[i] !== fillValue) {
+ throw new Error(`Found '${buf[i]}' in buffer, expected '${fillValue}'.`);
+ }
+ }
+}
+
+test_fill_buffer(0, [2, 3, 4]);
+test_fill_buffer(5, [2, 7, 3, 2, 1]);
+
+const deferred = Promise.withResolvers();
+const buffer3 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+dylib.symbols.nonblocking_buffer(buffer3, buffer3.length).then(() => {
+ deferred.resolve();
+});
+await deferred.promise;
+
+let start = performance.now();
+dylib.symbols.sleep_blocking(100);
+assert(performance.now() - start >= 100);
+
+start = performance.now();
+const promise_2 = dylib.symbols.sleep_nonblocking(100).then(() => {
+ console.log("After");
+ assert(performance.now() - start >= 100);
+});
+console.log("Before");
+assert(performance.now() - start < 100);
+
+// Await to make sure `sleep_nonblocking` calls and logs before we proceed
+await promise_2;
+
+// Test calls with callback parameters
+const logCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => console.log("logCallback"),
+);
+const logManyParametersCallback = new Deno.UnsafeCallback({
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+}, (u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, pointer) => {
+ const view = new Deno.UnsafePointerView(pointer);
+ const copy_buffer = new Uint8Array(8);
+ view.copyInto(copy_buffer);
+ console.log(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, ...copy_buffer);
+});
+const returnU8Callback = new Deno.UnsafeCallback(
+ { parameters: [], result: "u8" },
+ () => 8,
+);
+const returnBufferCallback = new Deno.UnsafeCallback({
+ parameters: [],
+ result: "buffer",
+}, () => {
+ return buffer;
+});
+const add10Callback = new Deno.UnsafeCallback({
+ parameters: ["u8"],
+ result: "u8",
+}, (value) => value + 10);
+const throwCallback = new Deno.UnsafeCallback({
+ parameters: [],
+ result: "void",
+}, () => {
+ throw new TypeError("hi");
+});
+
+assertThrows(
+ () => {
+ dylib.symbols.call_fn_ptr(throwCallback.pointer);
+ },
+ TypeError,
+ "hi",
+);
+
+const { call_stored_function } = dylib.symbols;
+
+dylib.symbols.call_fn_ptr(logCallback.pointer);
+dylib.symbols.call_fn_ptr_many_parameters(logManyParametersCallback.pointer);
+dylib.symbols.call_fn_ptr_return_u8(returnU8Callback.pointer);
+dylib.symbols.call_fn_ptr_return_buffer(returnBufferCallback.pointer);
+dylib.symbols.store_function(logCallback.pointer);
+call_stored_function();
+dylib.symbols.store_function_2(add10Callback.pointer);
+dylib.symbols.call_stored_function_2(20);
+
+function logManyParametersFast(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) {
+ return symbols.log_many_parameters(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s);
+};
+testOptimized(
+ logManyParametersFast,
+ () => logManyParametersFast(
+ 255, 65535, 4294967295, 4294967296, 123.456, 789.876, -1, -2, -3, -4, -1000, 1000,
+ 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910
+ )
+);
+
+// Some ABIs rely on the convention to zero/sign-extend arguments by the caller to optimize the callee function.
+// If the trampoline did not zero/sign-extend arguments, this would return 256 instead of the expected 0 (in optimized builds)
+function castU8U32Fast(x) { return symbols.cast_u8_u32(x); };
+testOptimized(castU8U32Fast, () => castU8U32Fast(256));
+
+// Some ABIs rely on the convention to expect garbage in the bits beyond the size of the return value to optimize the callee function.
+// If the trampoline did not zero/sign-extend the return value, this would return 256 instead of the expected 0 (in optimized builds)
+function castU32U8Fast(x) { return symbols.cast_u32_u8(x); };
+testOptimized(castU32U8Fast, () => castU32U8Fast(256));
+
+// Generally the trampoline tail-calls into the FFI function, but in certain cases (e.g. when returning 8 or 16 bit integers)
+// the tail call is not possible and a new stack frame must be created. We need enough parameters to have some on the stack
+function addManyU16Fast(a, b, c, d, e, f, g, h, i, j, k, l, m) {
+ return symbols.add_many_u16(a, b, c, d, e, f, g, h, i, j, k, l, m);
+};
+// N.B. V8 does not currently follow Aarch64 Apple's calling convention.
+// The current implementation of the JIT trampoline follows the V8 incorrect calling convention. This test covers the use-case
+// and is expected to fail once Deno uses a V8 version with the bug fixed.
+// The V8 bug is being tracked in https://bugs.chromium.org/p/v8/issues/detail?id=13171
+testOptimized(addManyU16Fast, () => addManyU16Fast(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
+
+
+const nestedCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => {
+ dylib.symbols.call_stored_function_2(10);
+ },
+);
+dylib.symbols.store_function(nestedCallback.pointer);
+
+dylib.symbols.store_function(null);
+dylib.symbols.store_function_2(null);
+
+let counter = 0;
+const addToFooCallback = new Deno.UnsafeCallback({
+ parameters: [],
+ result: "void",
+}, () => counter++);
+
+// Test thread safe callbacks
+assertEquals(counter, 0);
+addToFooCallback.ref();
+await dylib.symbols.call_fn_ptr_thread_safe(addToFooCallback.pointer);
+addToFooCallback.unref();
+logCallback.ref();
+await dylib.symbols.call_fn_ptr_thread_safe(logCallback.pointer);
+logCallback.unref();
+assertEquals(counter, 1);
+returnU8Callback.ref();
+await dylib.symbols.call_fn_ptr_return_u8_thread_safe(returnU8Callback.pointer);
+// Purposefully do not unref returnU8Callback: Instead use it to test close() unrefing.
+
+// Test statics
+assertEquals(dylib.symbols.static_u32, 42);
+assertEquals(dylib.symbols.static_i64, -1242464576485);
+assert(
+ typeof dylib.symbols.static_ptr === "object"
+);
+assertEquals(
+ Object.keys(dylib.symbols.static_ptr).length, 0
+);
+const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr);
+assertEquals(view.getUint32(), 42);
+
+// Test struct returning
+const rect_sync = dylib.symbols.make_rect(10, 20, 100, 200);
+assertInstanceOf(rect_sync, Uint8Array);
+assertEquals(rect_sync.length, 4 * 8);
+assertEquals(Array.from(new Float64Array(rect_sync.buffer)), [10, 20, 100, 200]);
+// Test struct passing
+dylib.symbols.print_rect(rect_sync);
+// Test struct passing asynchronously
+await dylib.symbols.print_rect_async(rect_sync);
+dylib.symbols.print_rect(new Float64Array([20, 20, 100, 200]));
+// Test struct returning asynchronously
+const rect_async = await dylib.symbols.make_rect_async(10, 20, 100, 200);
+assertInstanceOf(rect_async, Uint8Array);
+assertEquals(rect_async.length, 4 * 8);
+assertEquals(Array.from(new Float64Array(rect_async.buffer)), [10, 20, 100, 200]);
+
+// Test complex, mixed struct returning and passing
+const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, Deno.UnsafePointer.create(12456789), new Uint32Array([8, 32]));
+assertEquals(mixedStruct.length, 56);
+assertEquals(Array.from(mixedStruct.subarray(0, 4)), [3, 0, 0, 0]);
+assertEquals(new Float32Array(mixedStruct.buffer, 4, 1)[0], 12.515000343322754);
+assertEquals(new Float64Array(mixedStruct.buffer, 8, 4), new Float64Array(rect_async.buffer));
+assertEquals(new BigUint64Array(mixedStruct.buffer, 40, 1)[0], 12456789n);
+assertEquals(new Uint32Array(mixedStruct.buffer, 48, 2), new Uint32Array([8, 32]));
+dylib.symbols.print_mixed(mixedStruct);
+
+const cb = new Deno.UnsafeCallback({
+ parameters: [{ struct: Rect }],
+ result: { struct: Rect },
+}, (innerRect) => {
+ innerRect = new Float64Array(innerRect.buffer);
+ return new Float64Array([innerRect[0] + 10, innerRect[1] + 10, innerRect[2] + 10, innerRect[3] + 10]);
+});
+
+const cbFfi = new Deno.UnsafeFnPointer(cb.pointer, cb.definition);
+const cbResult = new Float64Array(cbFfi.call(rect_async).buffer);
+assertEquals(Array.from(cbResult), [20, 30, 110, 210]);
+
+cb.close();
+
+const arrayBuffer = view.getArrayBuffer(4);
+const uint32Array = new Uint32Array(arrayBuffer);
+assertEquals(arrayBuffer.byteLength, 4);
+assertEquals(uint32Array.length, 1);
+assertEquals(uint32Array[0], 42);
+uint32Array[0] = 55; // MUTATES!
+assertEquals(uint32Array[0], 55);
+assertEquals(view.getUint32(), 55);
+
+
+{
+ // Test UnsafePointer APIs
+ assertEquals(Deno.UnsafePointer.create(0), null);
+ const createdPointer = Deno.UnsafePointer.create(1);
+ assertNotEquals(createdPointer, null);
+ assertEquals(typeof createdPointer, "object");
+ assertEquals(Deno.UnsafePointer.value(null), 0);
+ assertEquals(Deno.UnsafePointer.value(createdPointer), 1);
+ assert(Deno.UnsafePointer.equals(null, null));
+ assertFalse(Deno.UnsafePointer.equals(null, createdPointer));
+ assertFalse(Deno.UnsafePointer.equals(Deno.UnsafePointer.create(2), createdPointer));
+ // Do not allow offsetting from null, `create` function should be used instead.
+ assertThrows(() => Deno.UnsafePointer.offset(null, 5));
+ const offsetPointer = Deno.UnsafePointer.offset(createdPointer, 5);
+ assertEquals(Deno.UnsafePointer.value(offsetPointer), 6);
+ const zeroPointer = Deno.UnsafePointer.offset(offsetPointer, -6);
+ assertEquals(Deno.UnsafePointer.value(zeroPointer), 0);
+ assertEquals(zeroPointer, null);
+}
+
+// Test non-UTF-8 characters
+
+const charView = new Deno.UnsafePointerView(dylib.symbols.static_char);
+
+const charArrayBuffer = charView.getArrayBuffer(14);
+const uint8Array = new Uint8Array(charArrayBuffer);
+assertEquals([...uint8Array], [
+ 0xC0, 0xC1, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+ 0x00
+]);
+
+// Check that `getCString` works equally to `TextDecoder`
+assertEquals(charView.getCString(), new TextDecoder().decode(uint8Array.subarray(0, uint8Array.length - 1)));
+
+// Check a selection of various invalid UTF-8 sequences in C strings and verify
+// that the `getCString` API does not cause unexpected behaviour.
+for (const charBuffer of [
+ Uint8Array.from([0xA0, 0xA1, 0x00]),
+ Uint8Array.from([0xE2, 0x28, 0xA1, 0x00]),
+ Uint8Array.from([0xE2, 0x82, 0x28, 0x00]),
+ Uint8Array.from([0xF0, 0x28, 0x8C, 0xBC, 0x00]),
+ Uint8Array.from([0xF0, 0x90, 0x28, 0xBC, 0x00]),
+ Uint8Array.from([0xF0, 0x28, 0x8C, 0x28, 0x00]),
+ Uint8Array.from([0xF8, 0xA1, 0xA1, 0xA1, 0xA1, 0x00]),
+ Uint8Array.from([0xFC, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0x00]),
+]) {
+ const charBufferPointer = Deno.UnsafePointer.of(charBuffer);
+ const charString = Deno.UnsafePointerView.getCString(charBufferPointer);
+ const charBufferPointerArrayBuffer = new Uint8Array(Deno.UnsafePointerView.getArrayBuffer(charBufferPointer, charBuffer.length - 1));
+ assertEquals(charString, new TextDecoder().decode(charBufferPointerArrayBuffer));
+ assertEquals([...charBuffer.subarray(0, charBuffer.length - 1)], [...charBufferPointerArrayBuffer]);
+}
+
+
+const bytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+function hash() { return dylib.symbols.hash(bytes, bytes.byteLength); };
+
+testOptimized(hash, () => hash());
+
+(function cleanup() {
+ dylib.close();
+ throwCallback.close();
+ logCallback.close();
+ logManyParametersCallback.close();
+ returnU8Callback.close();
+ returnBufferCallback.close();
+ add10Callback.close();
+ nestedCallback.close();
+ addToFooCallback.close();
+
+ const resourcesPost = Deno[Deno.internal].core.resources();
+
+ const preStr = JSON.stringify(resourcesPre, null, 2);
+ const postStr = JSON.stringify(resourcesPost, null, 2);
+ if (preStr !== postStr) {
+ throw new Error(
+ `Difference in open resources before dlopen and after closing:
+Before: ${preStr}
+After: ${postStr}`,
+ );
+ }
+
+ console.log("Correct number of resources");
+})();
+
+function assertIsOptimized(fn) {
+ const status = %GetOptimizationStatus(fn);
+ assert(status & (1 << 4), `expected ${fn.name} to be optimized, but wasn't`);
+}
+
+function testOptimized(fn, callback) {
+ %PrepareFunctionForOptimization(fn);
+ const r1 = callback();
+ if (r1 !== undefined) {
+ console.log(r1);
+ }
+ %OptimizeFunctionOnNextCall(fn);
+ const r2 = callback();
+ if (r2 !== undefined) {
+ console.log(r2);
+ }
+ assertIsOptimized(fn);
+}
diff --git a/tests/ffi/tests/thread_safe_test.js b/tests/ffi/tests/thread_safe_test.js
new file mode 100644
index 000000000..fffa61a04
--- /dev/null
+++ b/tests/ffi/tests/thread_safe_test.js
@@ -0,0 +1,105 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(libPath, {
+ store_function: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function: {
+ parameters: [],
+ result: "void",
+ },
+ call_stored_function_thread_safe: {
+ parameters: [],
+ result: "void",
+ },
+});
+
+let resolveWorker;
+let workerResponsePromise;
+
+const worker = new Worker(
+ new URL("./thread_safe_test_worker.js", import.meta.url).href,
+ { type: "module" },
+);
+
+worker.addEventListener("message", () => {
+ if (resolveWorker) {
+ resolveWorker();
+ }
+});
+
+const sendWorkerMessage = async (data) => {
+ workerResponsePromise = new Promise((res) => {
+ resolveWorker = res;
+ });
+ worker.postMessage(data);
+ await workerResponsePromise;
+};
+
+// Test step 1: Register main thread callback, trigger on worker thread
+
+const mainThreadCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => {
+ console.log("Callback on main thread");
+ },
+);
+
+mainThreadCallback.ref();
+
+dylib.symbols.store_function(mainThreadCallback.pointer);
+
+await sendWorkerMessage("call");
+
+// Test step 2: Register on worker thread, trigger on main thread
+
+await sendWorkerMessage("register");
+
+dylib.symbols.call_stored_function();
+
+// Unref both main and worker thread callbacks and terminate the worker: Note, the stored function pointer in lib is now dangling.
+
+dylib.symbols.store_function(null);
+
+mainThreadCallback.unref();
+await sendWorkerMessage("unref");
+worker.terminate();
+
+// Test step 3: Register a callback that will be the only thing left keeping the isolate from exiting.
+// Rely on it to keep Deno running until the callback comes in and unrefs the callback, after which Deno should exit.
+
+const cleanupCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => {
+ console.log("Callback being called");
+ // Defer the cleanup to give the spawned thread all the time it needs to properly shut down
+ setTimeout(() => cleanup(), 100);
+ },
+);
+
+cleanupCallback.ref();
+
+function cleanup() {
+ cleanupCallback.unref();
+ dylib.symbols.store_function(null);
+ mainThreadCallback.close();
+ cleanupCallback.close();
+ console.log("Isolate should now exit");
+}
+
+dylib.symbols.store_function(cleanupCallback.pointer);
+
+console.log(
+ "Calling callback, isolate should stay asleep until callback is called",
+);
+dylib.symbols.call_stored_function_thread_safe();
diff --git a/tests/ffi/tests/thread_safe_test_worker.js b/tests/ffi/tests/thread_safe_test_worker.js
new file mode 100644
index 000000000..fc4854436
--- /dev/null
+++ b/tests/ffi/tests/thread_safe_test_worker.js
@@ -0,0 +1,41 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(libPath, {
+ store_function: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function: {
+ parameters: [],
+ result: "void",
+ },
+});
+
+const callback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => {
+ console.log("Callback on worker thread");
+ },
+);
+
+callback.ref();
+
+self.addEventListener("message", ({ data }) => {
+ if (data === "register") {
+ dylib.symbols.store_function(callback.pointer);
+ } else if (data === "call") {
+ dylib.symbols.call_stored_function();
+ } else if (data === "unref") {
+ callback.close();
+ }
+ self.postMessage("done");
+});
diff --git a/tests/integration/js_unit_tests.rs b/tests/integration/js_unit_tests.rs
index 16aebd8c4..de7108d25 100644
--- a/tests/integration/js_unit_tests.rs
+++ b/tests/integration/js_unit_tests.rs
@@ -120,7 +120,7 @@ fn js_unit_test(test: String) {
.current_dir(util::root_path())
.arg("test")
.arg("--config")
- .arg("tests/config/deno.json")
+ .arg(util::deno_config_path())
.arg("--no-lock")
.arg("--unstable")
.arg("--location=http://127.0.0.1:4545/")
diff --git a/tests/integration/node_compat_tests.rs b/tests/integration/node_compat_tests.rs
index d592c75a5..e2b3c219c 100644
--- a/tests/integration/node_compat_tests.rs
+++ b/tests/integration/node_compat_tests.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use test_util as util;
+use util::deno_config_path;
use util::env_vars_for_npm_tests;
#[test]
@@ -9,7 +10,7 @@ fn node_compat_tests() {
.current_dir(util::root_path())
.arg("test")
.arg("--config")
- .arg("tests/config/deno.json")
+ .arg(deno_config_path())
.arg("--no-lock")
.arg("--unstable")
.arg("-A")
diff --git a/tests/integration/node_unit_tests.rs b/tests/integration/node_unit_tests.rs
index c99586fa1..7c5976bf6 100644
--- a/tests/integration/node_unit_tests.rs
+++ b/tests/integration/node_unit_tests.rs
@@ -4,6 +4,7 @@ use std::io::BufReader;
use std::time::Duration;
use std::time::Instant;
use test_util as util;
+use util::deno_config_path;
use util::env_vars_for_npm_tests;
util::unit_test_factory!(
@@ -97,7 +98,7 @@ fn node_unit_test(test: String) {
.current_dir(util::root_path())
.arg("test")
.arg("--config")
- .arg("tests/config/deno.json")
+ .arg(deno_config_path())
.arg("--no-lock")
.arg("--unstable")
// TODO(kt3k): This option is required to pass tls_test.ts,
diff --git a/tests/napi/.gitignore b/tests/napi/.gitignore
new file mode 100644
index 000000000..54de1ef34
--- /dev/null
+++ b/tests/napi/.gitignore
@@ -0,0 +1,7 @@
+package-lock.json
+
+# Test generated artifacts
+.swc
+*.dylib
+*.so
+*.dll
diff --git a/tests/napi/Cargo.toml b/tests/napi/Cargo.toml
new file mode 100644
index 000000000..611d6d550
--- /dev/null
+++ b/tests/napi/Cargo.toml
@@ -0,0 +1,22 @@
+# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "test_napi"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+publish = false
+repository.workspace = true
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+napi-sys = { version = "=2.2.2", default-features = false, features = ["napi7"] }
+
+[dev-dependencies]
+test_util.workspace = true
+
+[build-dependencies]
+napi-build = "1"
diff --git a/tests/napi/array_test.js b/tests/napi/array_test.js
new file mode 100644
index 000000000..572d3a899
--- /dev/null
+++ b/tests/napi/array_test.js
@@ -0,0 +1,19 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const array = loadTestLibrary();
+
+Deno.test("napi array new", function () {
+ const e = [0, "Hello", {}];
+ const r = array.test_array_new(e);
+ assertEquals(typeof r, "object");
+ assertEquals(r.length, 3);
+ assertEquals(e, r);
+});
+
+Deno.test("napi array new with length", function () {
+ const r = array.test_array_new_with_length(100);
+ assertEquals(typeof r, "object");
+ assertEquals(r.length, 100);
+});
diff --git a/tests/napi/arraybuffer_test.js b/tests/napi/arraybuffer_test.js
new file mode 100644
index 000000000..f55b9a78c
--- /dev/null
+++ b/tests/napi/arraybuffer_test.js
@@ -0,0 +1,24 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals, loadTestLibrary } from "./common.js";
+
+const typedarray = loadTestLibrary();
+
+Deno.test("napi arraybuffer detach", function () {
+ const buf = new ArrayBuffer(5);
+ assertEquals(buf.byteLength, 5);
+ typedarray.test_detached(buf);
+ assertEquals(buf.byteLength, 0);
+});
+
+Deno.test("napi arraybuffer is detached", function () {
+ const buf = new ArrayBuffer(5);
+ assertEquals(buf.byteLength, 5);
+ assert(!typedarray.is_detached(buf));
+ typedarray.test_detached(buf);
+ assert(typedarray.is_detached(buf));
+
+ [2, {}, "foo", null, undefined, new Uint8Array(10)].forEach((value) => {
+ assert(!typedarray.is_detached(value));
+ });
+});
diff --git a/tests/napi/async_test.js b/tests/napi/async_test.js
new file mode 100644
index 000000000..4d9ad99c2
--- /dev/null
+++ b/tests/napi/async_test.js
@@ -0,0 +1,16 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const asyncTask = loadTestLibrary();
+
+Deno.test("napi async task schedule", async () => {
+ let called = false;
+ await new Promise((resolve) => {
+ asyncTask.test_async_work(() => {
+ called = true;
+ resolve();
+ });
+ });
+ assertEquals(called, true);
+});
diff --git a/tests/napi/bigint_test.js b/tests/napi/bigint_test.js
new file mode 100644
index 000000000..4a9ada205
--- /dev/null
+++ b/tests/napi/bigint_test.js
@@ -0,0 +1,63 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, assertThrows, loadTestLibrary } from "./common.js";
+
+const bi = loadTestLibrary();
+
+Deno.test("cases", function () {
+ const cases = [
+ 0n,
+ -0n,
+ 1n,
+ -1n,
+ 100n,
+ 2121n,
+ -1233n,
+ 986583n,
+ -976675n,
+ 98765432213456789876546896323445679887645323232436587988766545658n,
+ -4350987086545760976737453646576078997096876957864353245245769809n,
+ ];
+
+ for (const num of cases) {
+ if (num > -(2n ** 63n) && num < 2n ** 63n) {
+ assertEquals(bi.testInt64(num), num);
+ assertEquals(bi.isLossless(num, true), true);
+ } else {
+ assertEquals(bi.isLossless(num, true), false);
+ }
+
+ if (num >= 0 && num < 2n ** 64n) {
+ assertEquals(bi.testUint64(num), num);
+ assertEquals(bi.isLossless(num, false), true);
+ } else {
+ assertEquals(bi.isLossless(num, false), false);
+ }
+
+ assertEquals(bi.testWords(num), num);
+ }
+});
+
+Deno.test(
+ // TODO(bartlomieju): fix this test
+ { ignore: true },
+ function tooBigBigInt() {
+ assertThrows(
+ () => bi.createTooBigBigInt(),
+ Error,
+ "Invalid argument",
+ );
+ },
+);
+
+Deno.test(
+ // TODO(bartlomieju): fix this test
+ { ignore: true },
+ function exceptionForwarding() {
+ assertThrows(
+ () => bi.makeBigIntWordsThrow(),
+ Error,
+ "Maximum BigInt size exceeded",
+ );
+ },
+);
diff --git a/tests/napi/build.rs b/tests/napi/build.rs
new file mode 100644
index 000000000..c2fe86a53
--- /dev/null
+++ b/tests/napi/build.rs
@@ -0,0 +1,7 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+extern crate napi_build;
+
+fn main() {
+ napi_build::setup();
+}
diff --git a/tests/napi/callback_test.js b/tests/napi/callback_test.js
new file mode 100644
index 000000000..98622d48d
--- /dev/null
+++ b/tests/napi/callback_test.js
@@ -0,0 +1,38 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const callback = loadTestLibrary();
+
+Deno.test("napi callback run with args", function () {
+ const result = callback.test_callback_run((a, b) => a + b, [1, 2]);
+ assertEquals(result, 3);
+});
+
+Deno.test("napi callback run with args (no return)", function () {
+ const result = callback.test_callback_run(() => {}, []);
+ assertEquals(result, undefined);
+});
+
+Deno.test("napi callback run with args (extra arguments)", function () {
+ const result = callback.test_callback_run((a, b) => a + b, [
+ "Hello,",
+ " Deno!",
+ 1,
+ 2,
+ 3,
+ ]);
+ assertEquals(result, "Hello, Deno!");
+});
+
+Deno.test("napi callback run with args & recv", function () {
+ const result = callback.test_callback_run_with_recv(
+ function () {
+ assertEquals(this, 69);
+ return this;
+ },
+ [],
+ 69,
+ );
+ assertEquals(result, 69);
+});
diff --git a/tests/napi/cleanup_hook_test.js b/tests/napi/cleanup_hook_test.js
new file mode 100644
index 000000000..0d83bc5b4
--- /dev/null
+++ b/tests/napi/cleanup_hook_test.js
@@ -0,0 +1,36 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const properties = loadTestLibrary();
+
+if (import.meta.main) {
+ properties.installCleanupHook();
+ console.log("installed cleanup hook");
+} else {
+ Deno.test("napi cleanup hook", async () => {
+ const { stdout, stderr, code } = await new Deno.Command(Deno.execPath(), {
+ args: [
+ "run",
+ "--config",
+ Deno.realPathSync("../config/deno.json"),
+ "--no-lock",
+ "--allow-read",
+ "--allow-run",
+ "--allow-ffi",
+ "--unstable-ffi",
+ import.meta.url,
+ ],
+ }).output();
+
+ assertEquals(new TextDecoder().decode(stderr), "");
+ assertEquals(code, 0);
+
+ const stdoutText = new TextDecoder().decode(stdout);
+ const stdoutLines = stdoutText.split("\n");
+ assertEquals(stdoutLines.length, 4);
+ assertEquals(stdoutLines[0], "installed cleanup hook");
+ assertEquals(stdoutLines[1], "cleanup(18)");
+ assertEquals(stdoutLines[2], "cleanup(42)");
+ });
+}
diff --git a/tests/napi/coerce_test.js b/tests/napi/coerce_test.js
new file mode 100644
index 000000000..64f014801
--- /dev/null
+++ b/tests/napi/coerce_test.js
@@ -0,0 +1,74 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const coerce = loadTestLibrary();
+
+Deno.test("napi coerce bool", function () {
+ assertEquals(coerce.test_coerce_bool(true), true);
+ assertEquals(coerce.test_coerce_bool(false), false);
+ assertEquals(coerce.test_coerce_bool(0), false);
+ assertEquals(coerce.test_coerce_bool(69), true);
+ assertEquals(coerce.test_coerce_bool(Number.MAX_SAFE_INTEGER), true);
+ assertEquals(coerce.test_coerce_bool(new Array(10)), true);
+ assertEquals(coerce.test_coerce_bool("Hello, Deno!"), true);
+ assertEquals(coerce.test_coerce_bool(Symbol("[[test]]")), true);
+ assertEquals(coerce.test_coerce_bool({}), true);
+ assertEquals(coerce.test_coerce_bool(() => false), true);
+ assertEquals(coerce.test_coerce_bool(undefined), false);
+ assertEquals(coerce.test_coerce_bool(null), false);
+});
+
+Deno.test("napi coerce number", function () {
+ assertEquals(coerce.test_coerce_number(true), 1);
+ assertEquals(coerce.test_coerce_number(false), 0);
+ assertEquals(coerce.test_coerce_number(0), 0);
+ assertEquals(coerce.test_coerce_number(69), 69);
+ assertEquals(coerce.test_coerce_number(""), 0);
+ assertEquals(
+ coerce.test_coerce_number(Number.MAX_SAFE_INTEGER),
+ Number.MAX_SAFE_INTEGER,
+ );
+ assertEquals(coerce.test_coerce_number(new Array(10)), NaN);
+ assertEquals(coerce.test_coerce_number("Hello, Deno!"), NaN);
+ assertEquals(coerce.test_coerce_number({}), NaN);
+ assertEquals(coerce.test_coerce_number(() => false), NaN);
+ assertEquals(coerce.test_coerce_number(undefined), NaN);
+ assertEquals(coerce.test_coerce_number(null), 0);
+});
+
+Deno.test("napi coerce string", function () {
+ assertEquals(coerce.test_coerce_string(true), "true");
+ assertEquals(coerce.test_coerce_string(false), "false");
+ assertEquals(coerce.test_coerce_string(0), "0");
+ assertEquals(coerce.test_coerce_string(69), "69");
+ assertEquals(coerce.test_coerce_string(""), "");
+ assertEquals(
+ coerce.test_coerce_string(Number.MAX_SAFE_INTEGER),
+ "9007199254740991",
+ );
+ assertEquals(coerce.test_coerce_string(new Array(10)), ",,,,,,,,,");
+ assertEquals(coerce.test_coerce_string("Hello, Deno!"), "Hello, Deno!");
+ assertEquals(coerce.test_coerce_string({}), "[object Object]");
+ assertEquals(coerce.test_coerce_string(() => false), "() => false");
+ assertEquals(coerce.test_coerce_string(undefined), "undefined");
+ assertEquals(coerce.test_coerce_string(null), "null");
+});
+
+Deno.test("napi coerce object", function () {
+ assertEquals(coerce.test_coerce_object(true), new Boolean(true));
+ assertEquals(coerce.test_coerce_object(false), new Boolean(false));
+ assertEquals(coerce.test_coerce_object(0), new Number(0));
+ assertEquals(coerce.test_coerce_object(69), new Number(0));
+ assertEquals(coerce.test_coerce_object(""), new String(""));
+ assertEquals(
+ coerce.test_coerce_object(Number.MAX_SAFE_INTEGER),
+ new Number(Number.MAX_SAFE_INTEGER),
+ );
+ assertEquals(coerce.test_coerce_object(new Array(10)), new Array(10));
+ assertEquals(
+ coerce.test_coerce_object("Hello, Deno!"),
+ new String("Hello, Deno!"),
+ );
+ assertEquals(coerce.test_coerce_object({}), {});
+});
diff --git a/tests/napi/common.js b/tests/napi/common.js
new file mode 100644
index 000000000..3a4f253ef
--- /dev/null
+++ b/tests/napi/common.js
@@ -0,0 +1,28 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+export {
+ assert,
+ assertEquals,
+ assertRejects,
+ assertThrows,
+} from "@std/assert/mod.ts";
+export { fromFileUrl } from "@std/path/mod.ts";
+import process from "node:process";
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+export const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+
+export function loadTestLibrary() {
+ const specifier = `${targetDir}/${libPrefix}test_napi.${libSuffix}`;
+
+ // Internal, used in ext/node
+ const module = {};
+ // Pass some flag, it should be ignored, but make sure it doesn't print
+ // warnings.
+ process.dlopen(module, specifier, 0);
+ return module.exports;
+}
diff --git a/tests/napi/date_test.js b/tests/napi/date_test.js
new file mode 100644
index 000000000..49eec5cca
--- /dev/null
+++ b/tests/napi/date_test.js
@@ -0,0 +1,17 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const date = loadTestLibrary();
+
+Deno.test("napi date", function () {
+ const dateTypeTestDate = date.createDate(1549183351);
+ assertEquals(date.isDate(dateTypeTestDate), true);
+ assertEquals(date.isDate(new Date(1549183351)), true);
+ assertEquals(date.isDate(2.4), false);
+ assertEquals(date.isDate("not a date"), false);
+ assertEquals(date.isDate(undefined), false);
+ assertEquals(date.isDate(null), false);
+ assertEquals(date.isDate({}), false);
+ assertEquals(date.getDateValue(new Date(1549183351)), 1549183351);
+});
diff --git a/tests/napi/env_test.js b/tests/napi/env_test.js
new file mode 100644
index 000000000..72b868a66
--- /dev/null
+++ b/tests/napi/env_test.js
@@ -0,0 +1,10 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, loadTestLibrary } from "./common.js";
+
+const env = loadTestLibrary();
+
+Deno.test("napi get global", function () {
+ const g = env.testNodeGlobal();
+ assert(g === globalThis);
+});
diff --git a/tests/napi/error_test.js b/tests/napi/error_test.js
new file mode 100644
index 000000000..57acde5c1
--- /dev/null
+++ b/tests/napi/error_test.js
@@ -0,0 +1,215 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import {
+ assert,
+ assertEquals,
+ assertThrows,
+ loadTestLibrary,
+} from "./common.js";
+
+const testError = loadTestLibrary();
+
+const theError = new Error("Some error");
+const theTypeError = new TypeError("Some type error");
+const theSyntaxError = new SyntaxError("Some syntax error");
+const theRangeError = new RangeError("Some type error");
+const theReferenceError = new ReferenceError("Some reference error");
+const theURIError = new URIError("Some URI error");
+const theEvalError = new EvalError("Some eval error");
+
+function assertThrowsWithCode(fn, value) {
+ let thrown = false;
+
+ try {
+ fn();
+ } catch (e) {
+ thrown = true;
+ assertEquals(e.message, value.message);
+ assertEquals(e.code, value.code);
+ } finally {
+ assert(thrown);
+ }
+}
+
+Deno.test("napi error", function () {
+ class MyError extends Error {}
+ const myError = new MyError("Some MyError");
+
+ // Test that native error object is correctly classed
+ assertEquals(testError.checkError(theError), true);
+
+ // Test that native type error object is correctly classed
+ assertEquals(testError.checkError(theTypeError), true);
+
+ // Test that native syntax error object is correctly classed
+ assertEquals(testError.checkError(theSyntaxError), true);
+
+ // Test that native range error object is correctly classed
+ assertEquals(testError.checkError(theRangeError), true);
+
+ // Test that native reference error object is correctly classed
+ assertEquals(testError.checkError(theReferenceError), true);
+
+ // Test that native URI error object is correctly classed
+ assertEquals(testError.checkError(theURIError), true);
+
+ // Test that native eval error object is correctly classed
+ assertEquals(testError.checkError(theEvalError), true);
+
+ // Test that class derived from native error is correctly classed
+ assertEquals(testError.checkError(myError), true);
+
+ // Test that non-error object is correctly classed
+ assertEquals(testError.checkError({}), false);
+
+ // Test that non-error primitive is correctly classed
+ assertEquals(testError.checkError("non-object"), false);
+
+ assertThrows(
+ () => {
+ testError.throwExistingError();
+ },
+ Error,
+ "existing error",
+ );
+
+ assertThrows(
+ () => {
+ testError.throwError();
+ },
+ Error,
+ "error",
+ );
+
+ assertThrows(
+ () => {
+ testError.throwRangeError();
+ },
+ RangeError,
+ "range error",
+ );
+
+ assertThrows(
+ () => {
+ testError.throwTypeError();
+ },
+ TypeError,
+ "type error",
+ );
+
+ // assertThrows(() => {
+ // testError.throwSyntaxError();
+ // }, "SyntaxError: syntax error");
+
+ [42, {}, [], Symbol("xyzzy"), true, "ball", undefined, null, NaN]
+ .forEach((value) => {
+ let thrown = false;
+
+ try {
+ testError.throwArbitrary(value);
+ } catch (e) {
+ thrown = true;
+ assertEquals(e, value);
+ } finally {
+ assert(thrown);
+ }
+ });
+
+ assertThrowsWithCode(
+ () => testError.throwErrorCode(),
+ {
+ code: "ERR_TEST_CODE",
+ message: "Error [error]",
+ },
+ );
+
+ assertThrowsWithCode(
+ () => testError.throwRangeErrorCode(),
+ {
+ code: "ERR_TEST_CODE",
+ message: "RangeError [range error]",
+ },
+ );
+
+ assertThrowsWithCode(
+ () => testError.throwTypeErrorCode(),
+ {
+ code: "ERR_TEST_CODE",
+ message: "TypeError [type error]",
+ },
+ );
+
+ // assertThrowsWithCode(
+ // () => testError.throwSyntaxErrorCode(),
+ // {
+ // code: "ERR_TEST_CODE",
+ // message: "SyntaxError [syntax error]",
+ // },
+ // );
+
+ let error = testError.createError();
+ assert(
+ error instanceof Error,
+ "expected error to be an instance of Error",
+ );
+ assertEquals(error.message, "error");
+
+ error = testError.createRangeError();
+ assert(
+ error instanceof RangeError,
+ "expected error to be an instance of RangeError",
+ );
+ assertEquals(error.message, "range error");
+
+ error = testError.createTypeError();
+ assert(
+ error instanceof TypeError,
+ "expected error to be an instance of TypeError",
+ );
+ assertEquals(error.message, "type error");
+
+ // TODO(bartlomieju): this is experimental API
+ // error = testError.createSyntaxError();
+ // assert(
+ // error instanceof SyntaxError,
+ // "expected error to be an instance of SyntaxError",
+ // );
+ // assertEquals(error.message, "syntax error");
+
+ error = testError.createErrorCode();
+ assert(
+ error instanceof Error,
+ "expected error to be an instance of Error",
+ );
+ assertEquals(error.code, "ERR_TEST_CODE");
+ assertEquals(error.message, "Error [error]");
+ assertEquals(error.name, "Error");
+
+ error = testError.createRangeErrorCode();
+ assert(
+ error instanceof RangeError,
+ "expected error to be an instance of RangeError",
+ );
+ assertEquals(error.message, "RangeError [range error]");
+ assertEquals(error.code, "ERR_TEST_CODE");
+ assertEquals(error.name, "RangeError");
+
+ error = testError.createTypeErrorCode();
+ assert(
+ error instanceof TypeError,
+ "expected error to be an instance of TypeError",
+ );
+ assertEquals(error.message, "TypeError [type error]");
+ assertEquals(error.code, "ERR_TEST_CODE");
+ assertEquals(error.name, "TypeError");
+
+ // TODO(bartlomieju): this is experimental API
+ // error = testError.createSyntaxErrorCode();
+ // assert(
+ // error instanceof SyntaxError,
+ // "expected error to be an instance of SyntaxError",
+ // );
+ // assertEquals(error.message, "SyntaxError [syntax error]");
+ // assertEquals(error.code, "ERR_TEST_CODE");
+ // assertEquals(error.name, "SyntaxError");
+});
diff --git a/tests/napi/init_test.js b/tests/napi/init_test.js
new file mode 100644
index 000000000..5f2507876
--- /dev/null
+++ b/tests/napi/init_test.js
@@ -0,0 +1,14 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, libSuffix } from "./common.js";
+
+const ops = Deno[Deno.internal].core.ops;
+
+Deno.test("ctr initialization (napi_module_register)", {
+ ignore: Deno.build.os == "windows",
+}, function () {
+ const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname;
+ const obj = ops.op_napi_open(path, {});
+ assert(obj != null);
+ assert(typeof obj === "object");
+});
diff --git a/tests/napi/make_callback_test.js b/tests/napi/make_callback_test.js
new file mode 100644
index 000000000..b1f7912ae
--- /dev/null
+++ b/tests/napi/make_callback_test.js
@@ -0,0 +1,53 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const mc = loadTestLibrary();
+
+Deno.test("napi makeCallback1", function () {
+ const resource = {};
+
+ let callCount = 0;
+ function cb() {
+ callCount++;
+ assertEquals(arguments.length, 0);
+ assertEquals(this, globalThis);
+ return 42;
+ }
+ assertEquals(mc.makeCallback(resource, globalThis, cb), 42);
+ assertEquals(callCount, 1);
+});
+
+Deno.test("napi makeCallback2", function () {
+ const resource = {};
+
+ let callCount = 0;
+ function cb(x) {
+ callCount++;
+ assertEquals(arguments.length, 1);
+ assertEquals(this, globalThis);
+ assertEquals(x, 1337);
+ return 42;
+ }
+ assertEquals(mc.makeCallback(resource, globalThis, cb, 1337), 42);
+ assertEquals(callCount, 1);
+});
+
+Deno.test("napi makeCallback3", function () {
+ const resource = {};
+
+ let callCount = 0;
+
+ function multiArgFunc(arg1, arg2, arg3) {
+ callCount++;
+ assertEquals(arg1, 1);
+ assertEquals(arg2, 2);
+ assertEquals(arg3, 3);
+ return 42;
+ }
+ assertEquals(
+ mc.makeCallback(resource, globalThis, multiArgFunc, 1, 2, 3),
+ 42,
+ );
+ assertEquals(callCount, 1);
+});
diff --git a/tests/napi/mem_test.js b/tests/napi/mem_test.js
new file mode 100644
index 000000000..bee8c194e
--- /dev/null
+++ b/tests/napi/mem_test.js
@@ -0,0 +1,11 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, loadTestLibrary } from "./common.js";
+
+const mem = loadTestLibrary();
+
+Deno.test("napi adjust external memory", function () {
+ const adjusted = mem.adjust_external_memory();
+ assert(typeof adjusted === "number");
+ assert(adjusted > 0);
+});
diff --git a/tests/napi/module.c b/tests/napi/module.c
new file mode 100644
index 000000000..4548aa37f
--- /dev/null
+++ b/tests/napi/module.c
@@ -0,0 +1,68 @@
+typedef struct napi_module {
+ int nm_version;
+ unsigned int nm_flags;
+ const char* nm_filename;
+ void* nm_register_func;
+ const char* nm_modname;
+ void* nm_priv;
+ void* reserved[4];
+} napi_module;
+
+#ifdef _WIN32
+#define NAPI_EXTERN __declspec(dllexport)
+#define NAPI_CDECL __cdecl
+#else
+#define NAPI_EXTERN __attribute__((visibility("default")))
+#define NAPI_CDECL
+#endif
+
+NAPI_EXTERN void NAPI_CDECL
+napi_module_register(napi_module* mod);
+
+#if defined(_MSC_VER)
+#if defined(__cplusplus)
+#define NAPI_C_CTOR(fn) \
+ static void NAPI_CDECL fn(void); \
+ namespace { \
+ struct fn##_ { \
+ fn##_() { fn(); } \
+ } fn##_v_; \
+ } \
+ static void NAPI_CDECL fn(void)
+#else // !defined(__cplusplus)
+#pragma section(".CRT$XCU", read)
+// The NAPI_C_CTOR macro defines a function fn that is called during CRT
+// initialization.
+// C does not support dynamic initialization of static variables and this code
+// simulates C++ behavior. Exporting the function pointer prevents it from being
+// optimized. See for details:
+// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
+#define NAPI_C_CTOR(fn) \
+ static void NAPI_CDECL fn(void); \
+ __declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \
+ fn; \
+ static void NAPI_CDECL fn(void)
+#endif // defined(__cplusplus)
+#else
+#define NAPI_C_CTOR(fn) \
+ static void fn(void) __attribute__((constructor)); \
+ static void fn(void)
+#endif
+
+#define NAPI_MODULE_TEST(modname, regfunc) \
+ static napi_module _module = { \
+ 1, \
+ 0, \
+ __FILE__, \
+ regfunc, \
+ #modname, \
+ 0, \
+ {0}, \
+ }; \
+ NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \
+
+void* init(void* env __attribute__((unused)), void* exports) {
+ return exports;
+}
+
+NAPI_MODULE_TEST(TEST_NAPI_MODULE_NAME, init)
diff --git a/tests/napi/numbers_test.js b/tests/napi/numbers_test.js
new file mode 100644
index 000000000..8a99c831d
--- /dev/null
+++ b/tests/napi/numbers_test.js
@@ -0,0 +1,18 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const numbers = loadTestLibrary();
+
+Deno.test("napi int32", function () {
+ assertEquals(numbers.test_int32(69), 69);
+ assertEquals(numbers.test_int32(Number.MAX_SAFE_INTEGER), -1);
+});
+
+Deno.test("napi int64", function () {
+ assertEquals(numbers.test_int64(69), 69);
+ assertEquals(
+ numbers.test_int64(Number.MAX_SAFE_INTEGER),
+ Number.MAX_SAFE_INTEGER,
+ );
+});
diff --git a/tests/napi/object_wrap_test.js b/tests/napi/object_wrap_test.js
new file mode 100644
index 000000000..f79fd08f8
--- /dev/null
+++ b/tests/napi/object_wrap_test.js
@@ -0,0 +1,41 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals, loadTestLibrary } from "./common.js";
+
+const objectWrap = loadTestLibrary();
+
+Deno.test("napi object wrap new", function () {
+ const obj = new objectWrap.NapiObject(0);
+ assertEquals(obj.get_value(), 0);
+ obj.set_value(10);
+ assertEquals(obj.get_value(), 10);
+ obj.increment();
+ assertEquals(obj.get_value(), 11);
+ obj.increment();
+ obj.set_value(10);
+ assertEquals(obj.get_value(), 10);
+ assertEquals(objectWrap.NapiObject.factory(), 64);
+});
+
+Deno.test("napi bind finalizer", function () {
+ const obj = {};
+ objectWrap.test_bind_finalizer(obj);
+});
+
+Deno.test("napi external finalizer", function () {
+ let obj = objectWrap.test_external_finalizer();
+ assert(obj);
+ obj = null;
+});
+
+Deno.test("napi external buffer", function () {
+ let buf = objectWrap.test_external_buffer();
+ assertEquals(buf, new Uint8Array([1, 2, 3]));
+ buf = null;
+});
+
+Deno.test("napi external arraybuffer", function () {
+ let buf = objectWrap.test_external_arraybuffer();
+ assertEquals(new Uint8Array(buf), new Uint8Array([1, 2, 3]));
+ buf = null;
+});
diff --git a/tests/napi/promise_test.js b/tests/napi/promise_test.js
new file mode 100644
index 000000000..e4bbfee6b
--- /dev/null
+++ b/tests/napi/promise_test.js
@@ -0,0 +1,34 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, assertRejects, loadTestLibrary } from "./common.js";
+
+const promise = loadTestLibrary();
+
+Deno.test("napi new promise and resolve", async () => {
+ const p = promise.test_promise_new();
+ promise.test_promise_resolve(69);
+
+ assertEquals(await p, 69);
+});
+
+Deno.test("napi new promise and reject", () => {
+ const p = promise.test_promise_new();
+
+ assertRejects(async () => {
+ promise.test_promise_reject(new TypeError("pikaboo"));
+ await p;
+ }, TypeError);
+});
+
+Deno.test("napi new promise and reject", async () => {
+ const p = promise.test_promise_new();
+ const is = promise.test_promise_is(p);
+ assertEquals(typeof is, "boolean");
+ assertEquals(is, true);
+
+ assertEquals(promise.test_promise_is(undefined), false);
+ assertEquals(promise.test_promise_is({}), false);
+ promise.test_promise_resolve(69);
+
+ assertEquals(await p, 69);
+});
diff --git a/tests/napi/properties_test.js b/tests/napi/properties_test.js
new file mode 100644
index 000000000..21a3555e8
--- /dev/null
+++ b/tests/napi/properties_test.js
@@ -0,0 +1,24 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const properties = loadTestLibrary();
+
+Deno.test("napi properties", () => {
+ assertEquals(properties.test_property_rw, 1);
+ properties.test_property_rw = 2;
+ assertEquals(properties.test_property_rw, 2);
+
+ assertEquals(properties.test_property_r, 1);
+
+ // https://github.com/denoland/deno/issues/17509
+ assertEquals(properties.test_simple_property, {
+ nice: 69,
+ });
+
+ assertEquals(properties.key_v8_string, 1);
+ const symbols = Object.getOwnPropertySymbols(properties);
+ assertEquals(symbols.length, 1);
+ assertEquals(symbols[0].description, "key_v8_symbol");
+ assertEquals(properties[symbols[0]], 1);
+});
diff --git a/tests/napi/src/array.rs b/tests/napi/src/array.rs
new file mode 100644
index 000000000..6df420eb5
--- /dev/null
+++ b/tests/napi/src/array.rs
@@ -0,0 +1,73 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_number;
+use napi_sys::ValueType::napi_object;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn test_array_new(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_object);
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_array(env, &mut value));
+
+ let mut length: u32 = 0;
+ assert_napi_ok!(napi_get_array_length(env, args[0], &mut length));
+
+ for i in 0..length {
+ let mut e: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_element(env, args[0], i, &mut e));
+ assert_napi_ok!(napi_set_element(env, value, i, e));
+ }
+
+ value
+}
+
+extern "C" fn test_array_new_with_length(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ let mut len: u32 = 0;
+ assert_napi_ok!(napi_get_value_uint32(env, args[0], &mut len));
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_array_with_length(env, len as usize, &mut value));
+
+ value
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_array_new", test_array_new),
+ napi_new_property!(
+ env,
+ "test_array_new_with_length",
+ test_array_new_with_length
+ ),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/arraybuffer.rs b/tests/napi/src/arraybuffer.rs
new file mode 100644
index 000000000..8402f5d86
--- /dev/null
+++ b/tests/napi/src/arraybuffer.rs
@@ -0,0 +1,52 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::*;
+
+extern "C" fn test_detached(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value = false;
+ assert_napi_ok!(napi_is_detached_arraybuffer(env, args[0], &mut value));
+ assert!(!value);
+ assert_napi_ok!(napi_detach_arraybuffer(env, args[0]));
+ assert_napi_ok!(napi_is_detached_arraybuffer(env, args[0], &mut value));
+ assert!(value);
+ args[0]
+}
+
+extern "C" fn is_detached(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value = false;
+ assert_napi_ok!(napi_is_detached_arraybuffer(env, args[0], &mut value));
+
+ let mut result = std::ptr::null_mut();
+ assert_napi_ok!(napi_get_boolean(env, value, &mut result));
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_detached", test_detached),
+ napi_new_property!(env, "is_detached", is_detached),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/async.rs b/tests/napi/src/async.rs
new file mode 100644
index 000000000..3d3827b51
--- /dev/null
+++ b/tests/napi/src/async.rs
@@ -0,0 +1,114 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::Status::napi_ok;
+use napi_sys::ValueType::napi_function;
+use napi_sys::*;
+use std::os::raw::c_char;
+use std::os::raw::c_void;
+use std::ptr;
+
+pub struct Baton {
+ called: bool,
+ func: napi_ref,
+ task: napi_async_work,
+}
+
+unsafe extern "C" fn execute(_env: napi_env, data: *mut c_void) {
+ let baton: &mut Baton = &mut *(data as *mut Baton);
+ assert!(!baton.called);
+ assert!(!baton.func.is_null());
+
+ baton.called = true;
+}
+
+unsafe extern "C" fn complete(
+ env: napi_env,
+ status: napi_status,
+ data: *mut c_void,
+) {
+ assert!(status == napi_ok);
+ let baton: Box<Baton> = Box::from_raw(data as *mut Baton);
+ assert!(baton.called);
+ assert!(!baton.func.is_null());
+
+ let mut global: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_global(env, &mut global));
+
+ let mut callback: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_reference_value(env, baton.func, &mut callback));
+
+ let mut _result: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_call_function(
+ env,
+ global,
+ callback,
+ 0,
+ ptr::null(),
+ &mut _result
+ ));
+ assert_napi_ok!(napi_delete_reference(env, baton.func));
+ assert_napi_ok!(napi_delete_async_work(env, baton.task));
+}
+
+extern "C" fn test_async_work(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_function);
+
+ let mut resource_name: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ "test_async_resource".as_ptr() as *const c_char,
+ usize::MAX,
+ &mut resource_name,
+ ));
+
+ let async_work: napi_async_work = ptr::null_mut();
+
+ let mut func: napi_ref = ptr::null_mut();
+ assert_napi_ok!(napi_create_reference(env, args[0], 1, &mut func));
+ let baton = Box::new(Baton {
+ called: false,
+ func,
+ task: async_work,
+ });
+ let mut async_work = baton.task;
+ let baton_ptr = Box::into_raw(baton) as *mut c_void;
+
+ assert_napi_ok!(napi_create_async_work(
+ env,
+ ptr::null_mut(),
+ resource_name,
+ Some(execute),
+ Some(complete),
+ baton_ptr,
+ &mut async_work,
+ ));
+ let mut baton = unsafe { Box::from_raw(baton_ptr as *mut Baton) };
+ baton.task = async_work;
+ Box::into_raw(baton);
+ assert_napi_ok!(napi_queue_async_work(env, async_work));
+
+ ptr::null_mut()
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties =
+ &[napi_new_property!(env, "test_async_work", test_async_work)];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/bigint.rs b/tests/napi/src/bigint.rs
new file mode 100644
index 000000000..d86782331
--- /dev/null
+++ b/tests/napi/src/bigint.rs
@@ -0,0 +1,205 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::cstr;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::Status::napi_pending_exception;
+use napi_sys::ValueType::napi_bigint;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn is_lossless(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 2);
+ assert_eq!(argc, 2);
+
+ let mut is_signed = false;
+ assert_napi_ok!(napi_get_value_bool(env, args[1], &mut is_signed));
+
+ let mut lossless = false;
+
+ if is_signed {
+ let mut input: i64 = 0;
+ assert_napi_ok!(napi_get_value_bigint_int64(
+ env,
+ args[0],
+ &mut input,
+ &mut lossless
+ ));
+ } else {
+ let mut input: u64 = 0;
+ assert_napi_ok!(napi_get_value_bigint_uint64(
+ env,
+ args[0],
+ &mut input,
+ &mut lossless
+ ));
+ }
+
+ let mut output: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_boolean(env, lossless, &mut output));
+
+ output
+}
+
+extern "C" fn test_int64(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, _argc, _) = napi_get_callback_info!(env, info, 2);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_bigint);
+
+ let mut input: i64 = 0;
+ let mut lossless = false;
+ assert_napi_ok!(napi_get_value_bigint_int64(
+ env,
+ args[0],
+ &mut input,
+ &mut lossless
+ ));
+
+ let mut output: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_bigint_int64(env, input, &mut output));
+
+ output
+}
+
+extern "C" fn test_uint64(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, _argc, _) = napi_get_callback_info!(env, info, 2);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_bigint);
+
+ let mut input: u64 = 0;
+ let mut lossless = false;
+ assert_napi_ok!(napi_get_value_bigint_uint64(
+ env,
+ args[0],
+ &mut input,
+ &mut lossless
+ ));
+
+ let mut output: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_bigint_uint64(env, input, &mut output));
+
+ output
+}
+
+extern "C" fn test_words(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, _argc, _) = napi_get_callback_info!(env, info, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_bigint);
+
+ let mut expected_work_count = 0;
+ assert_napi_ok!(napi_get_value_bigint_words(
+ env,
+ args[0],
+ ptr::null_mut(),
+ &mut expected_work_count,
+ ptr::null_mut()
+ ));
+
+ let mut sign_bit = 0;
+ let mut word_count: usize = 10;
+ let mut words: Vec<u64> = Vec::with_capacity(10);
+
+ assert_napi_ok!(napi_get_value_bigint_words(
+ env,
+ args[0],
+ &mut sign_bit,
+ &mut word_count,
+ words.as_mut_ptr(),
+ ));
+
+ assert_eq!(word_count, expected_work_count);
+ let mut output: napi_value = ptr::null_mut();
+
+ assert_napi_ok!(napi_create_bigint_words(
+ env,
+ sign_bit,
+ word_count,
+ words.as_ptr(),
+ &mut output,
+ ));
+ output
+}
+
+extern "C" fn create_too_big_big_int(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let sign_bit = 0;
+ let word_count = usize::MAX;
+ let words: Vec<u64> = Vec::with_capacity(10);
+
+ let mut output: napi_value = ptr::null_mut();
+ let result = unsafe {
+ napi_create_bigint_words(
+ env,
+ sign_bit,
+ word_count,
+ words.as_ptr(),
+ &mut output,
+ )
+ };
+ assert_eq!(result, 1);
+
+ output
+}
+
+extern "C" fn make_big_int_words_throw(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let words: Vec<u64> = Vec::with_capacity(10);
+ let mut output = ptr::null_mut();
+
+ let status = unsafe {
+ napi_create_bigint_words(env, 0, usize::MAX, words.as_ptr(), &mut output)
+ };
+
+ if status != napi_pending_exception {
+ unsafe {
+ napi_throw_error(
+ env,
+ ptr::null_mut(),
+ cstr!("Expected status 'napi_pending_exception'"),
+ )
+ };
+ }
+
+ ptr::null_mut()
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "isLossless", is_lossless),
+ napi_new_property!(env, "testInt64", test_int64),
+ napi_new_property!(env, "testUint64", test_uint64),
+ napi_new_property!(env, "testWords", test_words),
+ napi_new_property!(env, "createTooBigBigInt", create_too_big_big_int),
+ napi_new_property!(env, "makeBigIntWordsThrow", make_big_int_words_throw),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/callback.rs b/tests/napi/src/callback.rs
new file mode 100644
index 000000000..8909f5176
--- /dev/null
+++ b/tests/napi/src/callback.rs
@@ -0,0 +1,117 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_function;
+use napi_sys::ValueType::napi_object;
+use napi_sys::ValueType::napi_undefined;
+use napi_sys::*;
+use std::ptr;
+
+/// `test_callback_run((a, b) => a + b, [1, 2])` => 3
+extern "C" fn test_callback_run(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ // We want to have argv with size 4, even though the callback will have
+ // only two arguments. We'll assert that the remaining two args are undefined.
+ let (args, argc, _) = napi_get_callback_info!(env, info, 4);
+ assert_eq!(argc, 2);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_function);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[1], &mut ty));
+ assert_eq!(ty, napi_object);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[2], &mut ty));
+ assert_eq!(ty, napi_undefined);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[3], &mut ty));
+ assert_eq!(ty, napi_undefined);
+
+ let mut len = 0;
+ assert_napi_ok!(napi_get_array_length(env, args[1], &mut len));
+
+ let mut argv = Vec::with_capacity(len as usize);
+ for index in 0..len {
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_element(env, args[1], index, &mut value));
+ argv.push(value);
+ }
+ let mut global: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_global(env, &mut global));
+
+ let mut result: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_call_function(
+ env,
+ global,
+ args[0],
+ argv.len(),
+ argv.as_mut_ptr(),
+ &mut result,
+ ));
+
+ result
+}
+
+extern "C" fn test_callback_run_with_recv(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 3);
+ assert_eq!(argc, 3);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_function);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[1], &mut ty));
+ assert_eq!(ty, napi_object);
+
+ let mut len = 0;
+ assert_napi_ok!(napi_get_array_length(env, args[1], &mut len));
+
+ let mut argv = Vec::with_capacity(len as usize);
+ for index in 0..len {
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_element(env, args[1], index, &mut value));
+ argv.push(value);
+ }
+
+ let mut result: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_call_function(
+ env,
+ args[2], // recv
+ args[0], // cb
+ argv.len(),
+ argv.as_mut_ptr(),
+ &mut result,
+ ));
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_callback_run", test_callback_run),
+ napi_new_property!(
+ env,
+ "test_callback_run_with_recv",
+ test_callback_run_with_recv
+ ),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/coerce.rs b/tests/napi/src/coerce.rs
new file mode 100644
index 000000000..a40578384
--- /dev/null
+++ b/tests/napi/src/coerce.rs
@@ -0,0 +1,70 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn test_coerce_bool(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_coerce_to_bool(env, args[0], &mut value));
+ value
+}
+
+extern "C" fn test_coerce_number(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_coerce_to_number(env, args[0], &mut value));
+ value
+}
+
+extern "C" fn test_coerce_object(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_coerce_to_object(env, args[0], &mut value));
+ value
+}
+
+extern "C" fn test_coerce_string(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_coerce_to_string(env, args[0], &mut value));
+ value
+}
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_coerce_bool", test_coerce_bool),
+ napi_new_property!(env, "test_coerce_number", test_coerce_number),
+ napi_new_property!(env, "test_coerce_object", test_coerce_object),
+ napi_new_property!(env, "test_coerce_string", test_coerce_string),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/date.rs b/tests/napi/src/date.rs
new file mode 100644
index 000000000..4d3c155c3
--- /dev/null
+++ b/tests/napi/src/date.rs
@@ -0,0 +1,74 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_number;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn create_date(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ let mut time = -1.0;
+ assert_napi_ok!(napi_get_value_double(env, args[0], &mut time));
+
+ let mut date: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_date(env, time, &mut date));
+
+ date
+}
+
+extern "C" fn is_date(env: napi_env, info: napi_callback_info) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let date: napi_value = args[0];
+ let mut result: napi_value = std::ptr::null_mut();
+ let mut is_date = false;
+
+ assert_napi_ok!(napi_is_date(env, date, &mut is_date));
+ assert_napi_ok!(napi_get_boolean(env, is_date, &mut result));
+
+ result
+}
+
+extern "C" fn get_date_value(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let date: napi_value = args[0];
+ let mut result: napi_value = std::ptr::null_mut();
+ let mut value = 0.0;
+
+ assert_napi_ok!(napi_get_date_value(env, date, &mut value));
+ assert_napi_ok!(napi_create_double(env, value, &mut result));
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "createDate", create_date),
+ napi_new_property!(env, "isDate", is_date),
+ napi_new_property!(env, "getDateValue", get_date_value),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/env.rs b/tests/napi/src/env.rs
new file mode 100644
index 000000000..ebc6532a3
--- /dev/null
+++ b/tests/napi/src/env.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::*;
+
+extern "C" fn get_node_global(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (_, argc, _) = napi_get_callback_info!(env, info, 0);
+ assert_eq!(argc, 0);
+
+ let mut result: napi_value = std::ptr::null_mut();
+ assert_napi_ok!(napi_get_global(env, &mut result));
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties =
+ &[napi_new_property!(env, "testNodeGlobal", get_node_global)];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/error.rs b/tests/napi/src/error.rs
new file mode 100644
index 000000000..e0d79c836
--- /dev/null
+++ b/tests/napi/src/error.rs
@@ -0,0 +1,288 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::cstr;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn check_error(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+ let mut r = false;
+ assert_napi_ok!(napi_is_error(env, args[0], &mut r));
+ let mut result: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_boolean(env, r, &mut result));
+ result
+}
+
+extern "C" fn create_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("error"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_error(
+ env,
+ ptr::null_mut(),
+ message,
+ &mut result
+ ));
+ result
+}
+
+extern "C" fn create_range_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("range error"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_range_error(
+ env,
+ ptr::null_mut(),
+ message,
+ &mut result
+ ));
+ result
+}
+
+extern "C" fn create_type_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("type error"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_type_error(
+ env,
+ ptr::null_mut(),
+ message,
+ &mut result
+ ));
+ result
+}
+
+extern "C" fn create_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ let mut code: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("Error [error]"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ usize::MAX,
+ &mut code
+ ));
+ assert_napi_ok!(napi_create_error(env, code, message, &mut result));
+ result
+}
+
+extern "C" fn create_range_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ let mut code: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("RangeError [range error]"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ usize::MAX,
+ &mut code
+ ));
+ assert_napi_ok!(napi_create_range_error(env, code, message, &mut result));
+ result
+}
+
+extern "C" fn create_type_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut result: napi_value = ptr::null_mut();
+ let mut message: napi_value = ptr::null_mut();
+ let mut code: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("TypeError [type error]"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ usize::MAX,
+ &mut code
+ ));
+ assert_napi_ok!(napi_create_type_error(env, code, message, &mut result));
+ result
+}
+
+extern "C" fn throw_existing_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut message: napi_value = ptr::null_mut();
+ let mut error: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("existing error"),
+ usize::MAX,
+ &mut message
+ ));
+ assert_napi_ok!(napi_create_error(
+ env,
+ std::ptr::null_mut(),
+ message,
+ &mut error
+ ));
+ assert_napi_ok!(napi_throw(env, error));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_error(env, std::ptr::null_mut(), cstr!("error"),));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_range_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_range_error(
+ env,
+ std::ptr::null_mut(),
+ cstr!("range error"),
+ ));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_type_error(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_type_error(
+ env,
+ std::ptr::null_mut(),
+ cstr!("type error"),
+ ));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_arbitrary(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+ assert_napi_ok!(napi_throw(env, args[0]));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_error(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ cstr!("Error [error]"),
+ ));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_range_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_range_error(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ cstr!("RangeError [range error]"),
+ ));
+ std::ptr::null_mut()
+}
+
+extern "C" fn throw_type_error_code(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ assert_napi_ok!(napi_throw_type_error(
+ env,
+ cstr!("ERR_TEST_CODE"),
+ cstr!("TypeError [type error]"),
+ ));
+ std::ptr::null_mut()
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "checkError", check_error),
+ napi_new_property!(env, "throwExistingError", throw_existing_error),
+ napi_new_property!(env, "throwError", throw_error),
+ napi_new_property!(env, "throwRangeError", throw_range_error),
+ napi_new_property!(env, "throwTypeError", throw_type_error),
+ // NOTE(bartlomieju): currently experimental api
+ // napi_new_property!(env, "throwSyntaxError", throw_syntax_error),
+ napi_new_property!(env, "throwErrorCode", throw_error_code),
+ napi_new_property!(env, "throwRangeErrorCode", throw_range_error_code),
+ napi_new_property!(env, "throwTypeErrorCode", throw_type_error_code),
+ // NOTE(bartlomieju): currently experimental api
+ // napi_new_property!(env, "throwSyntaxErrorCode", throw_syntax_error_code),
+ napi_new_property!(env, "throwArbitrary", throw_arbitrary),
+ napi_new_property!(env, "createError", create_error),
+ napi_new_property!(env, "createRangeError", create_range_error),
+ napi_new_property!(env, "createTypeError", create_type_error),
+ // NOTE(bartlomieju): currently experimental api
+ // napi_new_property!(env, "createSyntaxError", create_syntax_error),
+ napi_new_property!(env, "createErrorCode", create_error_code),
+ napi_new_property!(env, "createRangeErrorCode", create_range_error_code),
+ napi_new_property!(env, "createTypeErrorCode", create_type_error_code),
+ // NOTE(bartlomieju): currently experimental api
+ // napi_new_property!(env, "createSyntaxErrorCode", create_syntax_error_code),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/finalizer.rs b/tests/napi/src/finalizer.rs
new file mode 100644
index 000000000..9769e775e
--- /dev/null
+++ b/tests/napi/src/finalizer.rs
@@ -0,0 +1,141 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_object;
+use napi_sys::*;
+use std::ptr;
+
+unsafe extern "C" fn finalize_cb(
+ _env: napi_env,
+ data: *mut ::std::os::raw::c_void,
+ hint: *mut ::std::os::raw::c_void,
+) {
+ assert!(data.is_null());
+ assert!(hint.is_null());
+}
+
+extern "C" fn test_bind_finalizer(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_object);
+
+ let obj = args[0];
+ unsafe {
+ napi_add_finalizer(
+ env,
+ obj,
+ ptr::null_mut(),
+ Some(finalize_cb),
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ };
+ obj
+}
+
+struct Thing {
+ _allocation: Vec<u8>,
+}
+
+unsafe extern "C" fn finalize_cb_drop(
+ _env: napi_env,
+ data: *mut ::std::os::raw::c_void,
+ hint: *mut ::std::os::raw::c_void,
+) {
+ let _ = Box::from_raw(data as *mut Thing);
+ assert!(hint.is_null());
+}
+
+extern "C" fn test_external_finalizer(
+ env: napi_env,
+ _: napi_callback_info,
+) -> napi_value {
+ let data = Box::into_raw(Box::new(Thing {
+ _allocation: vec![1, 2, 3],
+ }));
+
+ let mut result = ptr::null_mut();
+ assert_napi_ok!(napi_create_external(
+ env,
+ data as _,
+ Some(finalize_cb_drop),
+ ptr::null_mut(),
+ &mut result
+ ));
+ result
+}
+
+unsafe extern "C" fn finalize_cb_vec(
+ _env: napi_env,
+ data: *mut ::std::os::raw::c_void,
+ hint: *mut ::std::os::raw::c_void,
+) {
+ let _ = Vec::from_raw_parts(data as *mut u8, 3, 3);
+ assert!(hint.is_null());
+}
+
+extern "C" fn test_external_buffer(
+ env: napi_env,
+ _: napi_callback_info,
+) -> napi_value {
+ let mut result = ptr::null_mut();
+ let buf: Vec<u8> = vec![1, 2, 3];
+ assert_napi_ok!(napi_create_external_buffer(
+ env,
+ 3,
+ buf.as_ptr() as _,
+ Some(finalize_cb_vec),
+ ptr::null_mut(),
+ &mut result
+ ));
+ std::mem::forget(buf);
+
+ result
+}
+
+extern "C" fn test_external_arraybuffer(
+ env: napi_env,
+ _: napi_callback_info,
+) -> napi_value {
+ let mut result = ptr::null_mut();
+ let buf: Vec<u8> = vec![1, 2, 3];
+ assert_napi_ok!(napi_create_external_arraybuffer(
+ env,
+ buf.as_ptr() as _,
+ 3,
+ Some(finalize_cb_vec),
+ ptr::null_mut(),
+ &mut result
+ ));
+ std::mem::forget(buf);
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_bind_finalizer", test_bind_finalizer),
+ napi_new_property!(env, "test_external_finalizer", test_external_finalizer),
+ napi_new_property!(env, "test_external_buffer", test_external_buffer),
+ napi_new_property!(
+ env,
+ "test_external_arraybuffer",
+ test_external_arraybuffer
+ ),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/lib.rs b/tests/napi/src/lib.rs
new file mode 100644
index 000000000..b9f93fbd6
--- /dev/null
+++ b/tests/napi/src/lib.rs
@@ -0,0 +1,171 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+#![allow(clippy::all)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+use std::ffi::c_void;
+
+use napi_sys::*;
+
+pub mod array;
+pub mod arraybuffer;
+pub mod r#async;
+pub mod bigint;
+pub mod callback;
+pub mod coerce;
+pub mod date;
+pub mod env;
+pub mod error;
+pub mod finalizer;
+pub mod make_callback;
+pub mod mem;
+pub mod numbers;
+pub mod object_wrap;
+pub mod primitives;
+pub mod promise;
+pub mod properties;
+pub mod strings;
+pub mod symbol;
+pub mod tsfn;
+pub mod typedarray;
+
+#[macro_export]
+macro_rules! cstr {
+ ($s: literal) => {{
+ std::ffi::CString::new($s).unwrap().into_raw()
+ }};
+}
+
+#[macro_export]
+macro_rules! assert_napi_ok {
+ ($call: expr) => {{
+ assert_eq!(unsafe { $call }, napi_sys::Status::napi_ok);
+ }};
+}
+
+#[macro_export]
+macro_rules! napi_get_callback_info {
+ ($env: expr, $callback_info: expr, $size: literal) => {{
+ let mut args = [std::ptr::null_mut(); $size];
+ let mut argc = $size;
+ let mut this = std::ptr::null_mut();
+ crate::assert_napi_ok!(napi_get_cb_info(
+ $env,
+ $callback_info,
+ &mut argc,
+ args.as_mut_ptr(),
+ &mut this,
+ std::ptr::null_mut(),
+ ));
+ (args, argc, this)
+ }};
+}
+
+#[macro_export]
+macro_rules! napi_new_property {
+ ($env: expr, $name: expr, $value: expr) => {
+ napi_property_descriptor {
+ utf8name: concat!($name, "\0").as_ptr() as *const std::os::raw::c_char,
+ name: std::ptr::null_mut(),
+ method: Some($value),
+ getter: None,
+ setter: None,
+ data: std::ptr::null_mut(),
+ attributes: 0,
+ value: std::ptr::null_mut(),
+ }
+ };
+}
+
+extern "C" fn cleanup(arg: *mut c_void) {
+ println!("cleanup({})", arg as i64);
+}
+
+extern "C" fn remove_this_hook(arg: *mut c_void) {
+ let env = arg as napi_env;
+ unsafe { napi_remove_env_cleanup_hook(env, Some(remove_this_hook), arg) };
+}
+
+static SECRET: i64 = 42;
+static WRONG_SECRET: i64 = 17;
+static THIRD_SECRET: i64 = 18;
+
+extern "C" fn install_cleanup_hook(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (_args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 0);
+
+ unsafe {
+ napi_add_env_cleanup_hook(env, Some(cleanup), WRONG_SECRET as *mut c_void);
+ napi_add_env_cleanup_hook(env, Some(cleanup), SECRET as *mut c_void);
+ napi_add_env_cleanup_hook(env, Some(cleanup), THIRD_SECRET as *mut c_void);
+ napi_add_env_cleanup_hook(env, Some(remove_this_hook), env as *mut c_void);
+ napi_remove_env_cleanup_hook(
+ env,
+ Some(cleanup),
+ WRONG_SECRET as *mut c_void,
+ );
+ }
+
+ std::ptr::null_mut()
+}
+
+pub fn init_cleanup_hook(env: napi_env, exports: napi_value) {
+ let properties = &[napi_new_property!(
+ env,
+ "installCleanupHook",
+ install_cleanup_hook
+ )];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
+
+#[no_mangle]
+unsafe extern "C" fn napi_register_module_v1(
+ env: napi_env,
+ _: napi_value,
+) -> napi_value {
+ #[cfg(windows)]
+ {
+ napi_sys::setup();
+ }
+
+ // We create a fresh exports object and leave the passed
+ // exports object empty.
+ //
+ // https://github.com/denoland/deno/issues/17349
+ let mut exports = std::ptr::null_mut();
+ assert_napi_ok!(napi_create_object(env, &mut exports));
+
+ strings::init(env, exports);
+ numbers::init(env, exports);
+ typedarray::init(env, exports);
+ arraybuffer::init(env, exports);
+ array::init(env, exports);
+ env::init(env, exports);
+ error::init(env, exports);
+ finalizer::init(env, exports);
+ primitives::init(env, exports);
+ properties::init(env, exports);
+ promise::init(env, exports);
+ coerce::init(env, exports);
+ object_wrap::init(env, exports);
+ callback::init(env, exports);
+ r#async::init(env, exports);
+ date::init(env, exports);
+ tsfn::init(env, exports);
+ mem::init(env, exports);
+ bigint::init(env, exports);
+ symbol::init(env, exports);
+ make_callback::init(env, exports);
+
+ init_cleanup_hook(env, exports);
+
+ exports
+}
diff --git a/tests/napi/src/make_callback.rs b/tests/napi/src/make_callback.rs
new file mode 100644
index 000000000..945df3452
--- /dev/null
+++ b/tests/napi/src/make_callback.rs
@@ -0,0 +1,85 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::cstr;
+use napi_sys::ValueType::napi_function;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn make_callback(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ const MAX_ARGUMENTS: usize = 10;
+ const RESERVED_ARGUMENTS: usize = 3;
+
+ let mut args = [std::ptr::null_mut(); MAX_ARGUMENTS];
+ let mut argc = MAX_ARGUMENTS;
+ assert_napi_ok!(napi_get_cb_info(
+ env,
+ info,
+ &mut argc,
+ args.as_mut_ptr(),
+ ptr::null_mut(),
+ ptr::null_mut(),
+ ));
+
+ assert!(argc > 0);
+ let resource = args[0];
+ let recv = args[1];
+ let func = args[2];
+
+ let mut argv: Vec<napi_value> = Vec::new();
+ argv.resize(MAX_ARGUMENTS - RESERVED_ARGUMENTS, ptr::null_mut());
+ for i in RESERVED_ARGUMENTS..argc {
+ argv[i - RESERVED_ARGUMENTS] = args[i];
+ }
+
+ let mut func_type: napi_valuetype = -1;
+ assert_napi_ok!(napi_typeof(env, func, &mut func_type));
+
+ let mut resource_name = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("test"),
+ usize::MAX,
+ &mut resource_name
+ ));
+
+ let mut context: napi_async_context = ptr::null_mut();
+ assert_napi_ok!(napi_async_init(env, resource, resource_name, &mut context));
+
+ let mut result = ptr::null_mut();
+ assert_eq!(func_type, napi_function);
+ assert_napi_ok!(napi_make_callback(
+ env,
+ context,
+ recv,
+ func,
+ argc - RESERVED_ARGUMENTS,
+ argv.as_mut_ptr(),
+ &mut result
+ ));
+
+ assert_napi_ok!(napi_async_destroy(env, context));
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let mut fn_: napi_value = ptr::null_mut();
+
+ assert_napi_ok!(napi_create_function(
+ env,
+ ptr::null_mut(),
+ usize::MAX,
+ Some(make_callback),
+ ptr::null_mut(),
+ &mut fn_,
+ ));
+ assert_napi_ok!(napi_set_named_property(
+ env,
+ exports,
+ cstr!("makeCallback"),
+ fn_
+ ));
+}
diff --git a/tests/napi/src/mem.rs b/tests/napi/src/mem.rs
new file mode 100644
index 000000000..ebb6a5c7a
--- /dev/null
+++ b/tests/napi/src/mem.rs
@@ -0,0 +1,34 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_new_property;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn adjust_external_memory(
+ env: napi_env,
+ _: napi_callback_info,
+) -> napi_value {
+ let mut adjusted_value = 0;
+
+ assert_napi_ok!(napi_adjust_external_memory(env, 1024, &mut adjusted_value));
+
+ let mut result = ptr::null_mut();
+ assert_napi_ok!(napi_create_int64(env, adjusted_value, &mut result));
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[napi_new_property!(
+ env,
+ "adjust_external_memory",
+ adjust_external_memory
+ )];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/numbers.rs b/tests/napi/src/numbers.rs
new file mode 100644
index 000000000..777ccbfac
--- /dev/null
+++ b/tests/napi/src/numbers.rs
@@ -0,0 +1,60 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_number;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn test_int32(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ let mut int32 = -1;
+ assert_napi_ok!(napi_get_value_int32(env, args[0], &mut int32));
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_int32(env, int32, &mut value));
+ value
+}
+
+extern "C" fn test_int64(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ let mut int64 = -1;
+ assert_napi_ok!(napi_get_value_int64(env, args[0], &mut int64));
+
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_int64(env, int64, &mut value));
+ value
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_int32", test_int32),
+ napi_new_property!(env, "test_int64", test_int64),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/object_wrap.rs b/tests/napi/src/object_wrap.rs
new file mode 100644
index 000000000..d04107cf0
--- /dev/null
+++ b/tests/napi/src/object_wrap.rs
@@ -0,0 +1,156 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_number;
+use napi_sys::*;
+use std::os::raw::c_char;
+use std::os::raw::c_void;
+use std::ptr;
+
+pub struct NapiObject {
+ counter: i32,
+ _wrapper: napi_ref,
+}
+
+impl NapiObject {
+ #[allow(clippy::new_ret_no_self)]
+ pub extern "C" fn new(env: napi_env, info: napi_callback_info) -> napi_value {
+ let mut new_target: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_new_target(env, info, &mut new_target));
+ let is_constructor = !new_target.is_null();
+
+ let (args, argc, this) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ if is_constructor {
+ let mut value = 0;
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value));
+
+ let mut wrapper: napi_ref = ptr::null_mut();
+ let obj = Box::new(Self {
+ counter: value,
+ _wrapper: wrapper,
+ });
+ assert_napi_ok!(napi_wrap(
+ env,
+ this,
+ Box::into_raw(obj) as *mut c_void,
+ None,
+ ptr::null_mut(),
+ &mut wrapper,
+ ));
+
+ return this;
+ }
+
+ unreachable!();
+ }
+
+ pub extern "C" fn set_value(
+ env: napi_env,
+ info: napi_callback_info,
+ ) -> napi_value {
+ let (args, argc, this) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+ let mut obj: *mut Self = ptr::null_mut();
+ assert_napi_ok!(napi_unwrap(
+ env,
+ this,
+ &mut obj as *mut _ as *mut *mut c_void
+ ));
+
+ assert_napi_ok!(napi_get_value_int32(env, args[0], &mut (*obj).counter));
+
+ ptr::null_mut()
+ }
+
+ pub extern "C" fn get_value(
+ env: napi_env,
+ info: napi_callback_info,
+ ) -> napi_value {
+ let (_args, argc, this) = napi_get_callback_info!(env, info, 0);
+ assert_eq!(argc, 0);
+ let mut obj: *mut Self = ptr::null_mut();
+ assert_napi_ok!(napi_unwrap(
+ env,
+ this,
+ &mut obj as *mut _ as *mut *mut c_void
+ ));
+
+ let mut num: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_int32(env, (*obj).counter, &mut num));
+
+ num
+ }
+
+ pub extern "C" fn increment(
+ env: napi_env,
+ info: napi_callback_info,
+ ) -> napi_value {
+ let (_args, argc, this) = napi_get_callback_info!(env, info, 0);
+ assert_eq!(argc, 0);
+ let mut obj: *mut Self = ptr::null_mut();
+ assert_napi_ok!(napi_unwrap(
+ env,
+ this,
+ &mut obj as *mut _ as *mut *mut c_void
+ ));
+
+ unsafe {
+ (*obj).counter += 1;
+ }
+
+ ptr::null_mut()
+ }
+
+ pub extern "C" fn factory(
+ env: napi_env,
+ info: napi_callback_info,
+ ) -> napi_value {
+ let (_args, argc, _this) = napi_get_callback_info!(env, info, 0);
+ assert_eq!(argc, 0);
+
+ let int64 = 64;
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_int64(env, int64, &mut value));
+ value
+ }
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let mut static_prop = napi_new_property!(env, "factory", NapiObject::factory);
+ static_prop.attributes = PropertyAttributes::static_;
+
+ let properties = &[
+ napi_new_property!(env, "set_value", NapiObject::set_value),
+ napi_new_property!(env, "get_value", NapiObject::get_value),
+ napi_new_property!(env, "increment", NapiObject::increment),
+ static_prop,
+ ];
+
+ let mut cons: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_define_class(
+ env,
+ "NapiObject\0".as_ptr() as *mut c_char,
+ usize::MAX,
+ Some(NapiObject::new),
+ ptr::null_mut(),
+ properties.len(),
+ properties.as_ptr(),
+ &mut cons,
+ ));
+
+ assert_napi_ok!(napi_set_named_property(
+ env,
+ exports,
+ "NapiObject\0".as_ptr() as *const c_char,
+ cons,
+ ));
+}
diff --git a/tests/napi/src/primitives.rs b/tests/napi/src/primitives.rs
new file mode 100644
index 000000000..28fb8ec3d
--- /dev/null
+++ b/tests/napi/src/primitives.rs
@@ -0,0 +1,30 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_new_property;
+use napi_sys::*;
+use std::ptr;
+
+extern "C" fn test_get_undefined(
+ env: napi_env,
+ _: napi_callback_info,
+) -> napi_value {
+ let mut result = ptr::null_mut();
+ assert_napi_ok!(napi_get_undefined(env, &mut result));
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[napi_new_property!(
+ env,
+ "test_get_undefined",
+ test_get_undefined
+ )];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/promise.rs b/tests/napi/src/promise.rs
new file mode 100644
index 000000000..82cd7a160
--- /dev/null
+++ b/tests/napi/src/promise.rs
@@ -0,0 +1,74 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::*;
+use std::ptr;
+
+static mut CURRENT_DEFERRED: napi_deferred = ptr::null_mut();
+
+extern "C" fn test_promise_new(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_promise(env, &mut CURRENT_DEFERRED, &mut value));
+ value
+}
+
+extern "C" fn test_promise_resolve(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ assert_napi_ok!(napi_resolve_deferred(env, CURRENT_DEFERRED, args[0]));
+ unsafe { CURRENT_DEFERRED = ptr::null_mut() };
+ ptr::null_mut()
+}
+
+extern "C" fn test_promise_reject(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ assert_napi_ok!(napi_reject_deferred(env, CURRENT_DEFERRED, args[0]));
+ unsafe { CURRENT_DEFERRED = ptr::null_mut() };
+ ptr::null_mut()
+}
+
+extern "C" fn test_promise_is(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut is_promise: bool = false;
+ assert_napi_ok!(napi_is_promise(env, args[0], &mut is_promise));
+
+ let mut result: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_boolean(env, is_promise, &mut result));
+
+ result
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_promise_new", test_promise_new),
+ napi_new_property!(env, "test_promise_resolve", test_promise_resolve),
+ napi_new_property!(env, "test_promise_reject", test_promise_reject),
+ napi_new_property!(env, "test_promise_is", test_promise_is),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/properties.rs b/tests/napi/src/properties.rs
new file mode 100644
index 000000000..43bef1949
--- /dev/null
+++ b/tests/napi/src/properties.rs
@@ -0,0 +1,113 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::cstr;
+use napi_sys::PropertyAttributes::*;
+use napi_sys::*;
+use std::ptr;
+
+static NICE: i64 = 69;
+
+fn init_constants(env: napi_env) -> napi_value {
+ let mut constants: napi_value = ptr::null_mut();
+ let mut value: napi_value = ptr::null_mut();
+
+ assert_napi_ok!(napi_create_object(env, &mut constants));
+ assert_napi_ok!(napi_create_int64(env, NICE, &mut value));
+ assert_napi_ok!(napi_set_named_property(
+ env,
+ constants,
+ cstr!("nice"),
+ value
+ ));
+ constants
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let mut number: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_double(env, 1.0, &mut number));
+
+ // Key name as napi_value representing `v8::String`
+ let mut name_value: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("key_v8_string"),
+ usize::MAX,
+ &mut name_value,
+ ));
+
+ // Key symbol
+ let mut symbol_description: napi_value = ptr::null_mut();
+ let mut name_symbol: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ cstr!("key_v8_symbol"),
+ usize::MAX,
+ &mut symbol_description,
+ ));
+ assert_napi_ok!(napi_create_symbol(
+ env,
+ symbol_description,
+ &mut name_symbol
+ ));
+
+ let properties = &[
+ napi_property_descriptor {
+ utf8name: cstr!("test_simple_property"),
+ name: ptr::null_mut(),
+ method: None,
+ getter: None,
+ setter: None,
+ data: ptr::null_mut(),
+ attributes: enumerable | writable,
+ value: init_constants(env),
+ },
+ napi_property_descriptor {
+ utf8name: cstr!("test_property_rw"),
+ name: ptr::null_mut(),
+ method: None,
+ getter: None,
+ setter: None,
+ data: ptr::null_mut(),
+ attributes: enumerable | writable,
+ value: number,
+ },
+ napi_property_descriptor {
+ utf8name: cstr!("test_property_r"),
+ name: ptr::null_mut(),
+ method: None,
+ getter: None,
+ setter: None,
+ data: ptr::null_mut(),
+ attributes: enumerable,
+ value: number,
+ },
+ napi_property_descriptor {
+ utf8name: ptr::null(),
+ name: name_value,
+ method: None,
+ getter: None,
+ setter: None,
+ data: ptr::null_mut(),
+ attributes: enumerable,
+ value: number,
+ },
+ napi_property_descriptor {
+ utf8name: ptr::null(),
+ name: name_symbol,
+ method: None,
+ getter: None,
+ setter: None,
+ data: ptr::null_mut(),
+ attributes: enumerable,
+ value: number,
+ },
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/strings.rs b/tests/napi/src/strings.rs
new file mode 100644
index 000000000..301ab23df
--- /dev/null
+++ b/tests/napi/src/strings.rs
@@ -0,0 +1,49 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_string;
+use napi_sys::*;
+
+extern "C" fn test_utf8(env: napi_env, info: napi_callback_info) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_string);
+
+ args[0]
+}
+
+extern "C" fn test_utf16(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_string);
+
+ args[0]
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ // utf8
+ napi_new_property!(env, "test_utf8", test_utf8),
+ // utf16
+ napi_new_property!(env, "test_utf16", test_utf16),
+ // latin1
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/symbol.rs b/tests/napi/src/symbol.rs
new file mode 100644
index 000000000..6387d449f
--- /dev/null
+++ b/tests/napi/src/symbol.rs
@@ -0,0 +1,39 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use napi_sys::ValueType::napi_string;
+use napi_sys::*;
+
+extern "C" fn symbol_new(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+
+ let mut description: napi_value = std::ptr::null_mut();
+
+ if argc >= 1 {
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_string);
+ description = args[0];
+ }
+
+ let mut symbol: napi_value = std::ptr::null_mut();
+ assert_napi_ok!(napi_create_symbol(env, description, &mut symbol));
+
+ symbol
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[napi_new_property!(env, "symbolNew", symbol_new)];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/src/tsfn.rs b/tests/napi/src/tsfn.rs
new file mode 100644
index 000000000..dabc96f83
--- /dev/null
+++ b/tests/napi/src/tsfn.rs
@@ -0,0 +1,108 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+// This test performs initialization similar to napi-rs.
+// https://github.com/napi-rs/napi-rs/commit/a5a04a4e545f268769cc78e2bd6c45af4336aac3
+
+use napi_sys as sys;
+use std::ffi::c_char;
+use std::ffi::c_void;
+use std::ptr;
+
+macro_rules! check_status_or_panic {
+ ($code:expr, $msg:expr) => {{
+ let c = $code;
+ match c {
+ sys::Status::napi_ok => {}
+ _ => panic!($msg),
+ }
+ }};
+}
+
+fn create_custom_gc(env: sys::napi_env) {
+ let mut custom_gc_fn = ptr::null_mut();
+ check_status_or_panic!(
+ unsafe {
+ sys::napi_create_function(
+ env,
+ "custom_gc".as_ptr() as *const c_char,
+ 9,
+ Some(empty),
+ ptr::null_mut(),
+ &mut custom_gc_fn,
+ )
+ },
+ "Create Custom GC Function in napi_register_module_v1 failed"
+ );
+ let mut async_resource_name = ptr::null_mut();
+ check_status_or_panic!(
+ unsafe {
+ sys::napi_create_string_utf8(
+ env,
+ "CustomGC".as_ptr() as *const c_char,
+ 8,
+ &mut async_resource_name,
+ )
+ },
+ "Create async resource string in napi_register_module_v1 napi_register_module_v1"
+ );
+ let mut custom_gc_tsfn = ptr::null_mut();
+ check_status_or_panic!(
+ unsafe {
+ sys::napi_create_threadsafe_function(
+ env,
+ custom_gc_fn,
+ ptr::null_mut(),
+ async_resource_name,
+ 0,
+ 1,
+ ptr::null_mut(),
+ Some(custom_gc_finalize),
+ ptr::null_mut(),
+ Some(custom_gc),
+ &mut custom_gc_tsfn,
+ )
+ },
+ "Create Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
+ );
+ check_status_or_panic!(
+ unsafe { sys::napi_unref_threadsafe_function(env, custom_gc_tsfn) },
+ "Unref Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
+ );
+}
+
+unsafe extern "C" fn empty(
+ _env: sys::napi_env,
+ _info: sys::napi_callback_info,
+) -> sys::napi_value {
+ ptr::null_mut()
+}
+
+unsafe extern "C" fn custom_gc_finalize(
+ _env: sys::napi_env,
+ _finalize_data: *mut c_void,
+ _finalize_hint: *mut c_void,
+) {
+}
+
+extern "C" fn custom_gc(
+ env: sys::napi_env,
+ _js_callback: sys::napi_value,
+ _context: *mut c_void,
+ data: *mut c_void,
+) {
+ let mut ref_count = 0;
+ check_status_or_panic!(
+ unsafe {
+ sys::napi_reference_unref(env, data as sys::napi_ref, &mut ref_count)
+ },
+ "Failed to unref Buffer reference in Custom GC"
+ );
+ check_status_or_panic!(
+ unsafe { sys::napi_delete_reference(env, data as sys::napi_ref) },
+ "Failed to delete Buffer reference in Custom GC"
+ );
+}
+
+pub fn init(env: sys::napi_env, _exports: sys::napi_value) {
+ create_custom_gc(env);
+}
diff --git a/tests/napi/src/typedarray.rs b/tests/napi/src/typedarray.rs
new file mode 100644
index 000000000..b512bd32f
--- /dev/null
+++ b/tests/napi/src/typedarray.rs
@@ -0,0 +1,157 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use core::ffi::c_void;
+use napi_sys::Status::napi_ok;
+use napi_sys::TypedarrayType;
+use napi_sys::ValueType::napi_number;
+use napi_sys::ValueType::napi_object;
+use napi_sys::*;
+use std::os::raw::c_char;
+use std::ptr;
+
+extern "C" fn test_multiply(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 2);
+ assert_eq!(argc, 2);
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[0], &mut ty));
+ assert_eq!(ty, napi_object);
+
+ let input_array = args[0];
+ let mut is_typed_array = false;
+ assert!(
+ unsafe { napi_is_typedarray(env, input_array, &mut is_typed_array) }
+ == napi_ok
+ );
+
+ let mut ty = -1;
+ assert_napi_ok!(napi_typeof(env, args[1], &mut ty));
+ assert_eq!(ty, napi_number);
+
+ let mut multiplier: f64 = 0.0;
+ assert_napi_ok!(napi_get_value_double(env, args[1], &mut multiplier));
+
+ let mut ty = -1;
+ let mut input_buffer = ptr::null_mut();
+ let mut byte_offset = 0;
+ let mut length = 0;
+
+ assert_napi_ok!(napi_get_typedarray_info(
+ env,
+ input_array,
+ &mut ty,
+ &mut length,
+ ptr::null_mut(),
+ &mut input_buffer,
+ &mut byte_offset,
+ ));
+
+ let mut data = ptr::null_mut();
+ let mut byte_length = 0;
+
+ assert_napi_ok!(napi_get_arraybuffer_info(
+ env,
+ input_buffer,
+ &mut data,
+ &mut byte_length
+ ));
+
+ let mut output_buffer = ptr::null_mut();
+ let mut output_ptr = ptr::null_mut();
+ assert_napi_ok!(napi_create_arraybuffer(
+ env,
+ byte_length,
+ &mut output_ptr,
+ &mut output_buffer,
+ ));
+
+ let mut output_array: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_typedarray(
+ env,
+ ty,
+ length,
+ output_buffer,
+ byte_offset,
+ &mut output_array,
+ ));
+
+ if ty == TypedarrayType::uint8_array {
+ let input_bytes = unsafe { (data as *mut u8).offset(byte_offset as isize) };
+ let output_bytes = output_ptr as *mut u8;
+ for i in 0..length {
+ unsafe {
+ *output_bytes.offset(i as isize) =
+ (*input_bytes.offset(i as isize) as f64 * multiplier) as u8;
+ }
+ }
+ } else if ty == TypedarrayType::float64_array {
+ let input_doubles =
+ unsafe { (data as *mut f64).offset(byte_offset as isize) };
+ let output_doubles = output_ptr as *mut f64;
+ for i in 0..length {
+ unsafe {
+ *output_doubles.offset(i as isize) =
+ *input_doubles.offset(i as isize) * multiplier;
+ }
+ }
+ } else {
+ assert_napi_ok!(napi_throw_error(
+ env,
+ ptr::null(),
+ "Typed array was of a type not expected by test.".as_ptr()
+ as *const c_char,
+ ));
+ return ptr::null_mut();
+ }
+
+ output_array
+}
+
+extern "C" fn test_external(
+ env: napi_env,
+ _info: napi_callback_info,
+) -> napi_value {
+ let mut arraybuffer: napi_value = ptr::null_mut();
+ let mut external: Box<[u8; 4]> = Box::new([0, 1, 2, 3]);
+ assert_napi_ok!(napi_create_external_arraybuffer(
+ env,
+ external.as_mut_ptr() as *mut c_void,
+ external.len(),
+ None,
+ ptr::null_mut(),
+ &mut arraybuffer,
+ ));
+
+ let mut typedarray: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_create_typedarray(
+ env,
+ TypedarrayType::uint8_array,
+ external.len(),
+ arraybuffer,
+ 0,
+ &mut typedarray,
+ ));
+
+ std::mem::forget(external); // Leak into JS land
+ typedarray
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[
+ napi_new_property!(env, "test_external", test_external),
+ napi_new_property!(env, "test_multiply", test_multiply),
+ ];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/strings_test.js b/tests/napi/strings_test.js
new file mode 100644
index 000000000..45cb133b2
--- /dev/null
+++ b/tests/napi/strings_test.js
@@ -0,0 +1,15 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const strings = loadTestLibrary();
+
+Deno.test("napi string utf8", function () {
+ assertEquals(strings.test_utf8(""), "");
+ assertEquals(strings.test_utf8("🦕"), "🦕");
+});
+
+Deno.test("napi string", function () {
+ assertEquals(strings.test_utf16(""), "");
+ assertEquals(strings.test_utf16("🦕"), "🦕");
+});
diff --git a/tests/napi/symbol_test.js b/tests/napi/symbol_test.js
new file mode 100644
index 000000000..d8edec023
--- /dev/null
+++ b/tests/napi/symbol_test.js
@@ -0,0 +1,49 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals, loadTestLibrary } from "./common.js";
+
+const testSymbol = loadTestLibrary();
+
+Deno.test("napi symbol1", () => {
+ const sym = testSymbol.symbolNew("test");
+ assertEquals(sym.toString(), "Symbol(test)");
+
+ const myObj = {};
+ const fooSym = testSymbol.symbolNew("foo");
+ const otherSym = testSymbol.symbolNew("bar");
+ myObj.foo = "bar";
+ myObj[fooSym] = "baz";
+ myObj[otherSym] = "bing";
+ assertEquals(myObj.foo, "bar");
+ assertEquals(myObj[fooSym], "baz");
+ assertEquals(myObj[otherSym], "bing");
+});
+
+Deno.test("napi symbol2", () => {
+ const sym = testSymbol.symbolNew("test");
+ assertEquals(sym.toString(), "Symbol(test)");
+
+ const myObj = {};
+ const fooSym = testSymbol.symbolNew("foo");
+ myObj.foo = "bar";
+ myObj[fooSym] = "baz";
+
+ assertEquals(Object.keys(myObj), ["foo"]);
+ assertEquals(Object.getOwnPropertyNames(myObj), ["foo"]);
+ assertEquals(Object.getOwnPropertySymbols(myObj), [fooSym]);
+});
+
+Deno.test("napi symbol3", () => {
+ assert(testSymbol.symbolNew() !== testSymbol.symbolNew());
+ assert(testSymbol.symbolNew("foo") !== testSymbol.symbolNew("foo"));
+ assert(testSymbol.symbolNew("foo") !== testSymbol.symbolNew("bar"));
+
+ const foo1 = testSymbol.symbolNew("foo");
+ const foo2 = testSymbol.symbolNew("foo");
+ const object = {
+ [foo1]: 1,
+ [foo2]: 2,
+ };
+ assertEquals(object[foo1], 1);
+ assertEquals(object[foo2], 2);
+});
diff --git a/tests/napi/tests/napi_tests.rs b/tests/napi/tests/napi_tests.rs
new file mode 100644
index 000000000..671699651
--- /dev/null
+++ b/tests/napi/tests/napi_tests.rs
@@ -0,0 +1,87 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use std::process::Command;
+use test_util::deno_cmd;
+use test_util::deno_config_path;
+use test_util::env_vars_for_npm_tests;
+use test_util::http_server;
+use test_util::napi_tests_path;
+
+#[cfg(debug_assertions)]
+const BUILD_VARIANT: &str = "debug";
+
+#[cfg(not(debug_assertions))]
+const BUILD_VARIANT: &str = "release";
+
+fn build() {
+ let mut build_plugin_base = Command::new("cargo");
+ let mut build_plugin =
+ build_plugin_base.arg("build").arg("-p").arg("test_napi");
+ if BUILD_VARIANT == "release" {
+ build_plugin = build_plugin.arg("--release");
+ }
+ let build_plugin_output = build_plugin.output().unwrap();
+ assert!(build_plugin_output.status.success());
+
+ // cc module.c -undefined dynamic_lookup -shared -Wl,-no_fixup_chains -dynamic -o module.dylib
+ #[cfg(not(target_os = "windows"))]
+ {
+ let out = if cfg!(target_os = "macos") {
+ "module.dylib"
+ } else {
+ "module.so"
+ };
+
+ let mut cc = Command::new("cc");
+
+ #[cfg(not(target_os = "macos"))]
+ let c_module = cc.arg("module.c").arg("-shared").arg("-o").arg(out);
+
+ #[cfg(target_os = "macos")]
+ let c_module = {
+ cc.arg("module.c")
+ .arg("-undefined")
+ .arg("dynamic_lookup")
+ .arg("-shared")
+ .arg("-Wl,-no_fixup_chains")
+ .arg("-dynamic")
+ .arg("-o")
+ .arg(out)
+ };
+ let c_module_output = c_module.output().unwrap();
+ assert!(c_module_output.status.success());
+ }
+}
+
+#[test]
+fn napi_tests() {
+ build();
+
+ let _http_guard = http_server();
+ let output = deno_cmd()
+ .current_dir(napi_tests_path())
+ .env("RUST_BACKTRACE", "1")
+ .arg("test")
+ .arg("--allow-read")
+ .arg("--allow-env")
+ .arg("--allow-ffi")
+ .arg("--allow-run")
+ .arg("--config")
+ .arg(deno_config_path())
+ .arg("--no-lock")
+ .arg(".")
+ .envs(env_vars_for_npm_tests())
+ .spawn()
+ .unwrap()
+ .wait_with_output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+
+ if !output.status.success() {
+ eprintln!("exit code {:?}", output.status.code());
+ println!("stdout {stdout}");
+ println!("stderr {stderr}");
+ }
+ assert!(output.status.success());
+}
diff --git a/tests/napi/typedarray_test.js b/tests/napi/typedarray_test.js
new file mode 100644
index 000000000..25729754a
--- /dev/null
+++ b/tests/napi/typedarray_test.js
@@ -0,0 +1,39 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals, loadTestLibrary } from "./common.js";
+
+const typedarray = loadTestLibrary();
+
+Deno.test("napi typedarray uint8", function () {
+ const byteArray = new Uint8Array([0, 1, 2]);
+ assertEquals(byteArray.length, 3);
+
+ const byteResult = typedarray.test_multiply(byteArray, 3);
+ assert(byteResult instanceof Uint8Array);
+ assertEquals(byteResult.length, 3);
+ assertEquals(byteResult[0], 0);
+ assertEquals(byteResult[1], 3);
+ assertEquals(byteResult[2], 6);
+});
+
+Deno.test("napi typedarray float64", function () {
+ const doubleArray = new Float64Array([0.0, 1.1, 2.2]);
+ assertEquals(doubleArray.length, 3);
+
+ const doubleResult = typedarray.test_multiply(doubleArray, -3);
+ assert(doubleResult instanceof Float64Array);
+ assertEquals(doubleResult.length, 3);
+ assertEquals(doubleResult[0], -0);
+ assertEquals(Math.round(10 * doubleResult[1]) / 10, -3.3);
+ assertEquals(Math.round(10 * doubleResult[2]) / 10, -6.6);
+});
+
+// TODO(bartlomieju): this test causes segfaults when used with jemalloc.
+// Node documentation provides a hint that this function is not supported by
+// other runtime like electron.
+// Deno.test("napi typedarray external", function () {
+// assertEquals(
+// new Uint8Array(typedarray.test_external()),
+// new Uint8Array([0, 1, 2, 3]),
+// );
+// });