From f60720090c7bd8cdf91d7aebd0c42e01c86b3b83 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 12 Feb 2024 13:46:50 -0700 Subject: chore: move test_ffi and test_nap to tests/ [WIP] (#22394) Moving some additional NAPI and. FFI tests out of the tree root. --- .dprint.json | 2 +- .gitignore | 6 +- Cargo.toml | 4 +- cli/bench/napi/bench.js | 2 +- cli/bench/napi/bench_node.mjs | 2 +- cli/napi/README.md | 10 +- ext/ffi/README.md | 2 +- test_ffi/Cargo.toml | 17 - test_ffi/README.md | 1 - test_ffi/src/lib.rs | 559 -------------------- test_ffi/tests/bench.js | 687 ------------------------ test_ffi/tests/event_loop_integration.ts | 78 --- test_ffi/tests/ffi_callback_errors.ts | 141 ----- test_ffi/tests/ffi_types.ts | 529 ------------------- test_ffi/tests/integration_tests.rs | 280 ---------- test_ffi/tests/test.js | 802 ----------------------------- test_ffi/tests/thread_safe_test.js | 105 ---- test_ffi/tests/thread_safe_test_worker.js | 41 -- test_napi/.gitignore | 7 - test_napi/Cargo.toml | 22 - test_napi/array_test.js | 19 - test_napi/arraybuffer_test.js | 24 - test_napi/async_test.js | 16 - test_napi/bigint_test.js | 63 --- test_napi/build.rs | 7 - test_napi/callback_test.js | 38 -- test_napi/cleanup_hook_test.js | 33 -- test_napi/coerce_test.js | 74 --- test_napi/common.js | 28 - test_napi/date_test.js | 17 - test_napi/env_test.js | 10 - test_napi/error_test.js | 215 -------- test_napi/init_test.js | 14 - test_napi/make_callback_test.js | 53 -- test_napi/mem_test.js | 11 - test_napi/module.c | 68 --- test_napi/numbers_test.js | 18 - test_napi/object_wrap_test.js | 41 -- test_napi/promise_test.js | 34 -- test_napi/properties_test.js | 24 - test_napi/src/array.rs | 73 --- test_napi/src/arraybuffer.rs | 52 -- test_napi/src/async.rs | 114 ---- test_napi/src/bigint.rs | 205 -------- test_napi/src/callback.rs | 117 ----- test_napi/src/coerce.rs | 70 --- test_napi/src/date.rs | 74 --- test_napi/src/env.rs | 31 -- test_napi/src/error.rs | 288 ----------- test_napi/src/finalizer.rs | 141 ----- test_napi/src/lib.rs | 171 ------ test_napi/src/make_callback.rs | 85 --- test_napi/src/mem.rs | 34 -- test_napi/src/numbers.rs | 60 --- test_napi/src/object_wrap.rs | 156 ------ test_napi/src/primitives.rs | 30 -- test_napi/src/promise.rs | 74 --- test_napi/src/properties.rs | 113 ---- test_napi/src/strings.rs | 49 -- test_napi/src/symbol.rs | 39 -- test_napi/src/tsfn.rs | 108 ---- test_napi/src/typedarray.rs | 157 ------ test_napi/strings_test.js | 15 - test_napi/symbol_test.js | 49 -- test_napi/tests/napi_tests.rs | 81 --- test_napi/typedarray_test.js | 39 -- test_util/src/lib.rs | 10 +- tests/config/deno.json | 3 +- tests/ffi/Cargo.toml | 17 + tests/ffi/README.md | 1 + tests/ffi/src/lib.rs | 559 ++++++++++++++++++++ tests/ffi/tests/bench.js | 687 ++++++++++++++++++++++++ tests/ffi/tests/event_loop_integration.ts | 78 +++ tests/ffi/tests/ffi_callback_errors.ts | 141 +++++ tests/ffi/tests/ffi_types.ts | 529 +++++++++++++++++++ tests/ffi/tests/integration_tests.rs | 302 +++++++++++ tests/ffi/tests/test.js | 802 +++++++++++++++++++++++++++++ tests/ffi/tests/thread_safe_test.js | 105 ++++ tests/ffi/tests/thread_safe_test_worker.js | 41 ++ tests/integration/js_unit_tests.rs | 2 +- tests/integration/node_compat_tests.rs | 3 +- tests/integration/node_unit_tests.rs | 3 +- tests/napi/.gitignore | 7 + tests/napi/Cargo.toml | 22 + tests/napi/array_test.js | 19 + tests/napi/arraybuffer_test.js | 24 + tests/napi/async_test.js | 16 + tests/napi/bigint_test.js | 63 +++ tests/napi/build.rs | 7 + tests/napi/callback_test.js | 38 ++ tests/napi/cleanup_hook_test.js | 36 ++ tests/napi/coerce_test.js | 74 +++ tests/napi/common.js | 28 + tests/napi/date_test.js | 17 + tests/napi/env_test.js | 10 + tests/napi/error_test.js | 215 ++++++++ tests/napi/init_test.js | 14 + tests/napi/make_callback_test.js | 53 ++ tests/napi/mem_test.js | 11 + tests/napi/module.c | 68 +++ tests/napi/numbers_test.js | 18 + tests/napi/object_wrap_test.js | 41 ++ tests/napi/promise_test.js | 34 ++ tests/napi/properties_test.js | 24 + tests/napi/src/array.rs | 73 +++ tests/napi/src/arraybuffer.rs | 52 ++ tests/napi/src/async.rs | 114 ++++ tests/napi/src/bigint.rs | 205 ++++++++ tests/napi/src/callback.rs | 117 +++++ tests/napi/src/coerce.rs | 70 +++ tests/napi/src/date.rs | 74 +++ tests/napi/src/env.rs | 31 ++ tests/napi/src/error.rs | 288 +++++++++++ tests/napi/src/finalizer.rs | 141 +++++ tests/napi/src/lib.rs | 171 ++++++ tests/napi/src/make_callback.rs | 85 +++ tests/napi/src/mem.rs | 34 ++ tests/napi/src/numbers.rs | 60 +++ tests/napi/src/object_wrap.rs | 156 ++++++ tests/napi/src/primitives.rs | 30 ++ tests/napi/src/promise.rs | 74 +++ tests/napi/src/properties.rs | 113 ++++ tests/napi/src/strings.rs | 49 ++ tests/napi/src/symbol.rs | 39 ++ tests/napi/src/tsfn.rs | 108 ++++ tests/napi/src/typedarray.rs | 157 ++++++ tests/napi/strings_test.js | 15 + tests/napi/symbol_test.js | 49 ++ tests/napi/tests/napi_tests.rs | 87 ++++ tests/napi/typedarray_test.js | 39 ++ 130 files changed, 6562 insertions(+), 6520 deletions(-) delete mode 100644 test_ffi/Cargo.toml delete mode 100644 test_ffi/README.md delete mode 100644 test_ffi/src/lib.rs delete mode 100644 test_ffi/tests/bench.js delete mode 100644 test_ffi/tests/event_loop_integration.ts delete mode 100644 test_ffi/tests/ffi_callback_errors.ts delete mode 100644 test_ffi/tests/ffi_types.ts delete mode 100644 test_ffi/tests/integration_tests.rs delete mode 100644 test_ffi/tests/test.js delete mode 100644 test_ffi/tests/thread_safe_test.js delete mode 100644 test_ffi/tests/thread_safe_test_worker.js delete mode 100644 test_napi/.gitignore delete mode 100644 test_napi/Cargo.toml delete mode 100644 test_napi/array_test.js delete mode 100644 test_napi/arraybuffer_test.js delete mode 100644 test_napi/async_test.js delete mode 100644 test_napi/bigint_test.js delete mode 100644 test_napi/build.rs delete mode 100644 test_napi/callback_test.js delete mode 100644 test_napi/cleanup_hook_test.js delete mode 100644 test_napi/coerce_test.js delete mode 100644 test_napi/common.js delete mode 100644 test_napi/date_test.js delete mode 100644 test_napi/env_test.js delete mode 100644 test_napi/error_test.js delete mode 100644 test_napi/init_test.js delete mode 100644 test_napi/make_callback_test.js delete mode 100644 test_napi/mem_test.js delete mode 100644 test_napi/module.c delete mode 100644 test_napi/numbers_test.js delete mode 100644 test_napi/object_wrap_test.js delete mode 100644 test_napi/promise_test.js delete mode 100644 test_napi/properties_test.js delete mode 100644 test_napi/src/array.rs delete mode 100644 test_napi/src/arraybuffer.rs delete mode 100644 test_napi/src/async.rs delete mode 100644 test_napi/src/bigint.rs delete mode 100644 test_napi/src/callback.rs delete mode 100644 test_napi/src/coerce.rs delete mode 100644 test_napi/src/date.rs delete mode 100644 test_napi/src/env.rs delete mode 100644 test_napi/src/error.rs delete mode 100644 test_napi/src/finalizer.rs delete mode 100644 test_napi/src/lib.rs delete mode 100644 test_napi/src/make_callback.rs delete mode 100644 test_napi/src/mem.rs delete mode 100644 test_napi/src/numbers.rs delete mode 100644 test_napi/src/object_wrap.rs delete mode 100644 test_napi/src/primitives.rs delete mode 100644 test_napi/src/promise.rs delete mode 100644 test_napi/src/properties.rs delete mode 100644 test_napi/src/strings.rs delete mode 100644 test_napi/src/symbol.rs delete mode 100644 test_napi/src/tsfn.rs delete mode 100644 test_napi/src/typedarray.rs delete mode 100644 test_napi/strings_test.js delete mode 100644 test_napi/symbol_test.js delete mode 100644 test_napi/tests/napi_tests.rs delete mode 100644 test_napi/typedarray_test.js create mode 100644 tests/ffi/Cargo.toml create mode 100644 tests/ffi/README.md create mode 100644 tests/ffi/src/lib.rs create mode 100644 tests/ffi/tests/bench.js create mode 100644 tests/ffi/tests/event_loop_integration.ts create mode 100644 tests/ffi/tests/ffi_callback_errors.ts create mode 100644 tests/ffi/tests/ffi_types.ts create mode 100644 tests/ffi/tests/integration_tests.rs create mode 100644 tests/ffi/tests/test.js create mode 100644 tests/ffi/tests/thread_safe_test.js create mode 100644 tests/ffi/tests/thread_safe_test_worker.js create mode 100644 tests/napi/.gitignore create mode 100644 tests/napi/Cargo.toml create mode 100644 tests/napi/array_test.js create mode 100644 tests/napi/arraybuffer_test.js create mode 100644 tests/napi/async_test.js create mode 100644 tests/napi/bigint_test.js create mode 100644 tests/napi/build.rs create mode 100644 tests/napi/callback_test.js create mode 100644 tests/napi/cleanup_hook_test.js create mode 100644 tests/napi/coerce_test.js create mode 100644 tests/napi/common.js create mode 100644 tests/napi/date_test.js create mode 100644 tests/napi/env_test.js create mode 100644 tests/napi/error_test.js create mode 100644 tests/napi/init_test.js create mode 100644 tests/napi/make_callback_test.js create mode 100644 tests/napi/mem_test.js create mode 100644 tests/napi/module.c create mode 100644 tests/napi/numbers_test.js create mode 100644 tests/napi/object_wrap_test.js create mode 100644 tests/napi/promise_test.js create mode 100644 tests/napi/properties_test.js create mode 100644 tests/napi/src/array.rs create mode 100644 tests/napi/src/arraybuffer.rs create mode 100644 tests/napi/src/async.rs create mode 100644 tests/napi/src/bigint.rs create mode 100644 tests/napi/src/callback.rs create mode 100644 tests/napi/src/coerce.rs create mode 100644 tests/napi/src/date.rs create mode 100644 tests/napi/src/env.rs create mode 100644 tests/napi/src/error.rs create mode 100644 tests/napi/src/finalizer.rs create mode 100644 tests/napi/src/lib.rs create mode 100644 tests/napi/src/make_callback.rs create mode 100644 tests/napi/src/mem.rs create mode 100644 tests/napi/src/numbers.rs create mode 100644 tests/napi/src/object_wrap.rs create mode 100644 tests/napi/src/primitives.rs create mode 100644 tests/napi/src/promise.rs create mode 100644 tests/napi/src/properties.rs create mode 100644 tests/napi/src/strings.rs create mode 100644 tests/napi/src/symbol.rs create mode 100644 tests/napi/src/tsfn.rs create mode 100644 tests/napi/src/typedarray.rs create mode 100644 tests/napi/strings_test.js create mode 100644 tests/napi/symbol_test.js create mode 100644 tests/napi/tests/napi_tests.rs create mode 100644 tests/napi/typedarray_test.js diff --git a/.dprint.json b/.dprint.json index 64e78ae13..ab12459e4 100644 --- a/.dprint.json +++ b/.dprint.json @@ -45,7 +45,7 @@ "cli/tsc/*typescript.js", "gh-pages", "target", - "test_ffi/tests/test.js", + "tests/ffi/tests/test.js", "test_util/std", "test_util/wpt", "third_party", diff --git a/.gitignore b/.gitignore index eab627b38..37f568324 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,9 @@ gclient_config.py_entries /std/hash/_wasm/target /tools/wpt/manifest.json /third_party/ -/test_napi/node_modules -/test_napi/build -/test_napi/third_party_tests/node_modules +/tests/napi/node_modules +/tests/napi/build +/tests/napi/third_party_tests/node_modules # MacOS generated files .DS_Store diff --git a/Cargo.toml b/Cargo.toml index 1c9d31109..c76dd166c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,10 @@ members = [ "ext/websocket", "ext/webstorage", "runtime", - "test_ffi", - "test_napi", "test_util", "tests", + "tests/ffi", + "tests/napi", ] exclude = ["test_util/std/hash/_wasm"] diff --git a/cli/bench/napi/bench.js b/cli/bench/napi/bench.js index d2aac63df..c12c7aacb 100644 --- a/cli/bench/napi/bench.js +++ b/cli/bench/napi/bench.js @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { loadTestLibrary } from "../../../test_napi/common.js"; +import { loadTestLibrary } from "../../../tests/napi/common.js"; const lib = loadTestLibrary(); diff --git a/cli/bench/napi/bench_node.mjs b/cli/bench/napi/bench_node.mjs index 2593aadd2..82e06f08f 100644 --- a/cli/bench/napi/bench_node.mjs +++ b/cli/bench/napi/bench_node.mjs @@ -2,7 +2,7 @@ import { bench, run } from "mitata"; import { createRequire } from "module"; const require = createRequire(import.meta.url); -const lib = require("../../../test_napi.node"); +const lib = require("../../../tests/napi.node"); bench("warmup", () => {}); bench("napi_get_undefined", () => lib.test_get_undefined(0)); diff --git a/cli/napi/README.md b/cli/napi/README.md index e40d4719d..ec637e1bd 100644 --- a/cli/napi/README.md +++ b/cli/napi/README.md @@ -50,11 +50,11 @@ Update the generated symbol lists using the script: deno run --allow-write tools/napi/generate_symbols_lists.js ``` -Add a test in [`/test_napi`](../../test_napi/). You can also refer to Node.js +Add a test in [`/tests/napi`](../../tests/napi/). You can also refer to Node.js test suite for Node-API. ```js -// test_napi/boolean_test.js +// tests/napi/boolean_test.js import { assertEquals, loadTestLibrary } from "./common.js"; const lib = loadTestLibrary(); Deno.test("napi get boolean", function () { @@ -64,7 +64,7 @@ Deno.test("napi get boolean", function () { ``` ```rust -// test_napi/src/boolean.rs +// tests/napi/src/boolean.rs use napi_sys::Status::napi_ok; use napi_sys::ValueType::napi_boolean; @@ -96,7 +96,7 @@ pub fn init(env: napi_env, exports: napi_value) { ``` ```diff -// test_napi/src/lib.rs +// tests/napi/src/lib.rs + mod boolean; @@ -114,4 +114,4 @@ unsafe extern "C" fn napi_register_module_v1( } ``` -Run the test using `cargo test -p test_napi`. +Run the test using `cargo test -p tests/napi`. diff --git a/ext/ffi/README.md b/ext/ffi/README.md index a821a3981..f2283c919 100644 --- a/ext/ffi/README.md +++ b/ext/ffi/README.md @@ -21,5 +21,5 @@ MacOS. To run benchmarks: ```bash -target/release/deno bench --allow-ffi --allow-read --unstable-ffi ./test_ffi/tests/bench.js +target/release/deno bench --allow-ffi --allow-read --unstable-ffi ./tests/ffi/tests/bench.js ``` diff --git a/test_ffi/Cargo.toml b/test_ffi/Cargo.toml deleted file mode 100644 index a5d2883ef..000000000 --- a/test_ffi/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -# 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/test_ffi/README.md b/test_ffi/README.md deleted file mode 100644 index 685385e4f..000000000 --- a/test_ffi/README.md +++ /dev/null @@ -1 +0,0 @@ -# `test_ffi` crate diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs deleted file mode 100644 index f6ee31eb8..000000000 --- a/test_ffi/src/lib.rs +++ /dev/null @@ -1,559 +0,0 @@ -// 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) { - 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 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 *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 = None; -static mut STORED_FUNCTION_2: Option u8> = None; - -#[no_mangle] -pub extern "C" fn store_function(func: Option) { - unsafe { STORED_FUNCTION = func }; - if func.is_none() { - println!("STORED_FUNCTION cleared"); - } -} - -#[no_mangle] -pub extern "C" fn store_function_2(func: Option 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/test_ffi/tests/bench.js b/test_ffi/tests/bench.js deleted file mode 100644 index 49884d32e..000000000 --- a/test_ffi/tests/bench.js +++ /dev/null @@ -1,687 +0,0 @@ -// 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/test_ffi/tests/event_loop_integration.ts b/test_ffi/tests/event_loop_integration.ts deleted file mode 100644 index d9ada6027..000000000 --- a/test_ffi/tests/event_loop_integration.ts +++ /dev/null @@ -1,78 +0,0 @@ -// 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/test_ffi/tests/ffi_callback_errors.ts b/test_ffi/tests/ffi_callback_errors.ts deleted file mode 100644 index dbd60636c..000000000 --- a/test_ffi/tests/ffi_callback_errors.ts +++ /dev/null @@ -1,141 +0,0 @@ -// 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; - -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/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts deleted file mode 100644 index 596662873..000000000 --- a/test_ffi/tests/ffi_types.ts +++ /dev/null @@ -1,529 +0,0 @@ -// 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 - remote.symbols.method1(0, true); - 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. - 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); - 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 = (() => G extends T ? 1 : 2) extends - (() => G extends U ? 1 : 2) ? true - : false; - -type AssertEqual< - Expected extends $, - Got extends $$, - $ = [Equal] extends [true] ? Expected - : ([Expected] extends [Got] ? never : Got), - $$ = [Equal] extends [true] ? Got - : ([Got] extends [Expected] ? never : Got), -> = never; - -type AssertNotEqual< - Expected extends $, - Got, - $ = [Equal] extends [true] ? never : Expected, -> = never; - -const enum FooEnum { - Foo, - Bar, -} -const foo = "u8" as Deno.NativeU8Enum; - -declare const brand: unique symbol; -class MyPointerClass {} -type MyPointer = Deno.PointerObject & { [brand]: MyPointerClass }; -const myPointer = "pointer" as Deno.NativeTypedPointer; -type MyFunctionDefinition = Deno.UnsafeCallbackDefinition< - [typeof foo, "u32"], - typeof myPointer ->; -const myFunction = "function" as Deno.NativeTypedFunction< - MyFunctionDefinition ->; - -type __Tests__ = [ - empty: AssertEqual< - { symbols: Record; close(): void }, - Deno.DynamicLibrary> - >, - 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 | null) => void; - }; - close(): void; - }, - Deno.DynamicLibrary< - { foo: { parameters: [typeof myFunction]; result: "void" } } - > - >, - typed_function_return: AssertEqual< - { - symbols: { - foo: () => Deno.PointerObject | null; - }; - close(): void; - }, - Deno.DynamicLibrary< - { foo: { parameters: []; result: typeof myFunction } } - > - >, -]; diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs deleted file mode 100644 index 642fbed03..000000000 --- a/test_ffi/tests/integration_tests.rs +++ /dev/null @@ -1,280 +0,0 @@ -// 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; - -#[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() - .arg("run") - .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() - .arg("check") - .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() - .arg("run") - .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() - .arg("run") - .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() - .arg("run") - .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/test_ffi/tests/test.js b/test_ffi/tests/test.js deleted file mode 100644 index 8e57cd34b..000000000 --- a/test_ffi/tests/test.js +++ /dev/null @@ -1,802 +0,0 @@ -// 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 "../../test_util/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->Data()` method also used to similarly return null pointers for all -// zero-sized buffers which would not match what `Local->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/test_ffi/tests/thread_safe_test.js b/test_ffi/tests/thread_safe_test.js deleted file mode 100644 index fffa61a04..000000000 --- a/test_ffi/tests/thread_safe_test.js +++ /dev/null @@ -1,105 +0,0 @@ -// 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/test_ffi/tests/thread_safe_test_worker.js b/test_ffi/tests/thread_safe_test_worker.js deleted file mode 100644 index fc4854436..000000000 --- a/test_ffi/tests/thread_safe_test_worker.js +++ /dev/null @@ -1,41 +0,0 @@ -// 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/test_napi/.gitignore b/test_napi/.gitignore deleted file mode 100644 index 54de1ef34..000000000 --- a/test_napi/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -package-lock.json - -# Test generated artifacts -.swc -*.dylib -*.so -*.dll diff --git a/test_napi/Cargo.toml b/test_napi/Cargo.toml deleted file mode 100644 index 611d6d550..000000000 --- a/test_napi/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -# 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/test_napi/array_test.js b/test_napi/array_test.js deleted file mode 100644 index 572d3a899..000000000 --- a/test_napi/array_test.js +++ /dev/null @@ -1,19 +0,0 @@ -// 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/test_napi/arraybuffer_test.js b/test_napi/arraybuffer_test.js deleted file mode 100644 index f55b9a78c..000000000 --- a/test_napi/arraybuffer_test.js +++ /dev/null @@ -1,24 +0,0 @@ -// 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/test_napi/async_test.js b/test_napi/async_test.js deleted file mode 100644 index 4d9ad99c2..000000000 --- a/test_napi/async_test.js +++ /dev/null @@ -1,16 +0,0 @@ -// 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/test_napi/bigint_test.js b/test_napi/bigint_test.js deleted file mode 100644 index 4a9ada205..000000000 --- a/test_napi/bigint_test.js +++ /dev/null @@ -1,63 +0,0 @@ -// 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/test_napi/build.rs b/test_napi/build.rs deleted file mode 100644 index c2fe86a53..000000000 --- a/test_napi/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -extern crate napi_build; - -fn main() { - napi_build::setup(); -} diff --git a/test_napi/callback_test.js b/test_napi/callback_test.js deleted file mode 100644 index 98622d48d..000000000 --- a/test_napi/callback_test.js +++ /dev/null @@ -1,38 +0,0 @@ -// 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/test_napi/cleanup_hook_test.js b/test_napi/cleanup_hook_test.js deleted file mode 100644 index f1f916175..000000000 --- a/test_napi/cleanup_hook_test.js +++ /dev/null @@ -1,33 +0,0 @@ -// 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", - "--allow-read", - "--allow-run", - "--allow-ffi", - "--unstable-ffi", - import.meta.url, - ], - }).output(); - - assertEquals(code, 0); - assertEquals(new TextDecoder().decode(stderr), ""); - - 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/test_napi/coerce_test.js b/test_napi/coerce_test.js deleted file mode 100644 index 64f014801..000000000 --- a/test_napi/coerce_test.js +++ /dev/null @@ -1,74 +0,0 @@ -// 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/test_napi/common.js b/test_napi/common.js deleted file mode 100644 index 71ac3f0bb..000000000 --- a/test_napi/common.js +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -export { - assert, - assertEquals, - assertRejects, - assertThrows, -} from "../test_util/std/assert/mod.ts"; -export { fromFileUrl } from "../test_util/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/test_napi/date_test.js b/test_napi/date_test.js deleted file mode 100644 index 49eec5cca..000000000 --- a/test_napi/date_test.js +++ /dev/null @@ -1,17 +0,0 @@ -// 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/test_napi/env_test.js b/test_napi/env_test.js deleted file mode 100644 index 72b868a66..000000000 --- a/test_napi/env_test.js +++ /dev/null @@ -1,10 +0,0 @@ -// 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/test_napi/error_test.js b/test_napi/error_test.js deleted file mode 100644 index 57acde5c1..000000000 --- a/test_napi/error_test.js +++ /dev/null @@ -1,215 +0,0 @@ -// 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/test_napi/init_test.js b/test_napi/init_test.js deleted file mode 100644 index 5f2507876..000000000 --- a/test_napi/init_test.js +++ /dev/null @@ -1,14 +0,0 @@ -// 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/test_napi/make_callback_test.js b/test_napi/make_callback_test.js deleted file mode 100644 index b1f7912ae..000000000 --- a/test_napi/make_callback_test.js +++ /dev/null @@ -1,53 +0,0 @@ -// 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/test_napi/mem_test.js b/test_napi/mem_test.js deleted file mode 100644 index bee8c194e..000000000 --- a/test_napi/mem_test.js +++ /dev/null @@ -1,11 +0,0 @@ -// 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/test_napi/module.c b/test_napi/module.c deleted file mode 100644 index 4548aa37f..000000000 --- a/test_napi/module.c +++ /dev/null @@ -1,68 +0,0 @@ -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/test_napi/numbers_test.js b/test_napi/numbers_test.js deleted file mode 100644 index 8a99c831d..000000000 --- a/test_napi/numbers_test.js +++ /dev/null @@ -1,18 +0,0 @@ -// 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/test_napi/object_wrap_test.js b/test_napi/object_wrap_test.js deleted file mode 100644 index f79fd08f8..000000000 --- a/test_napi/object_wrap_test.js +++ /dev/null @@ -1,41 +0,0 @@ -// 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/test_napi/promise_test.js b/test_napi/promise_test.js deleted file mode 100644 index e4bbfee6b..000000000 --- a/test_napi/promise_test.js +++ /dev/null @@ -1,34 +0,0 @@ -// 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/test_napi/properties_test.js b/test_napi/properties_test.js deleted file mode 100644 index 21a3555e8..000000000 --- a/test_napi/properties_test.js +++ /dev/null @@ -1,24 +0,0 @@ -// 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/test_napi/src/array.rs b/test_napi/src/array.rs deleted file mode 100644 index 6df420eb5..000000000 --- a/test_napi/src/array.rs +++ /dev/null @@ -1,73 +0,0 @@ -// 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/test_napi/src/arraybuffer.rs b/test_napi/src/arraybuffer.rs deleted file mode 100644 index 8402f5d86..000000000 --- a/test_napi/src/arraybuffer.rs +++ /dev/null @@ -1,52 +0,0 @@ -// 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/test_napi/src/async.rs b/test_napi/src/async.rs deleted file mode 100644 index 3d3827b51..000000000 --- a/test_napi/src/async.rs +++ /dev/null @@ -1,114 +0,0 @@ -// 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 = 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/test_napi/src/bigint.rs b/test_napi/src/bigint.rs deleted file mode 100644 index d86782331..000000000 --- a/test_napi/src/bigint.rs +++ /dev/null @@ -1,205 +0,0 @@ -// 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 = 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 = 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 = 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/test_napi/src/callback.rs b/test_napi/src/callback.rs deleted file mode 100644 index 8909f5176..000000000 --- a/test_napi/src/callback.rs +++ /dev/null @@ -1,117 +0,0 @@ -// 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/test_napi/src/coerce.rs b/test_napi/src/coerce.rs deleted file mode 100644 index a40578384..000000000 --- a/test_napi/src/coerce.rs +++ /dev/null @@ -1,70 +0,0 @@ -// 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/test_napi/src/date.rs b/test_napi/src/date.rs deleted file mode 100644 index 4d3c155c3..000000000 --- a/test_napi/src/date.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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/test_napi/src/env.rs b/test_napi/src/env.rs deleted file mode 100644 index ebc6532a3..000000000 --- a/test_napi/src/env.rs +++ /dev/null @@ -1,31 +0,0 @@ -// 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/test_napi/src/error.rs b/test_napi/src/error.rs deleted file mode 100644 index e0d79c836..000000000 --- a/test_napi/src/error.rs +++ /dev/null @@ -1,288 +0,0 @@ -// 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/test_napi/src/finalizer.rs b/test_napi/src/finalizer.rs deleted file mode 100644 index 9769e775e..000000000 --- a/test_napi/src/finalizer.rs +++ /dev/null @@ -1,141 +0,0 @@ -// 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, -} - -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 = 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 = 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/test_napi/src/lib.rs b/test_napi/src/lib.rs deleted file mode 100644 index b9f93fbd6..000000000 --- a/test_napi/src/lib.rs +++ /dev/null @@ -1,171 +0,0 @@ -// 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/test_napi/src/make_callback.rs b/test_napi/src/make_callback.rs deleted file mode 100644 index 945df3452..000000000 --- a/test_napi/src/make_callback.rs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 = 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/test_napi/src/mem.rs b/test_napi/src/mem.rs deleted file mode 100644 index ebb6a5c7a..000000000 --- a/test_napi/src/mem.rs +++ /dev/null @@ -1,34 +0,0 @@ -// 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/test_napi/src/numbers.rs b/test_napi/src/numbers.rs deleted file mode 100644 index 777ccbfac..000000000 --- a/test_napi/src/numbers.rs +++ /dev/null @@ -1,60 +0,0 @@ -// 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/test_napi/src/object_wrap.rs b/test_napi/src/object_wrap.rs deleted file mode 100644 index d04107cf0..000000000 --- a/test_napi/src/object_wrap.rs +++ /dev/null @@ -1,156 +0,0 @@ -// 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/test_napi/src/primitives.rs b/test_napi/src/primitives.rs deleted file mode 100644 index 28fb8ec3d..000000000 --- a/test_napi/src/primitives.rs +++ /dev/null @@ -1,30 +0,0 @@ -// 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/test_napi/src/promise.rs b/test_napi/src/promise.rs deleted file mode 100644 index 82cd7a160..000000000 --- a/test_napi/src/promise.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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/test_napi/src/properties.rs b/test_napi/src/properties.rs deleted file mode 100644 index 43bef1949..000000000 --- a/test_napi/src/properties.rs +++ /dev/null @@ -1,113 +0,0 @@ -// 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/test_napi/src/strings.rs b/test_napi/src/strings.rs deleted file mode 100644 index 301ab23df..000000000 --- a/test_napi/src/strings.rs +++ /dev/null @@ -1,49 +0,0 @@ -// 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/test_napi/src/symbol.rs b/test_napi/src/symbol.rs deleted file mode 100644 index 6387d449f..000000000 --- a/test_napi/src/symbol.rs +++ /dev/null @@ -1,39 +0,0 @@ -// 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/test_napi/src/tsfn.rs b/test_napi/src/tsfn.rs deleted file mode 100644 index dabc96f83..000000000 --- a/test_napi/src/tsfn.rs +++ /dev/null @@ -1,108 +0,0 @@ -// 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/test_napi/src/typedarray.rs b/test_napi/src/typedarray.rs deleted file mode 100644 index b512bd32f..000000000 --- a/test_napi/src/typedarray.rs +++ /dev/null @@ -1,157 +0,0 @@ -// 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/test_napi/strings_test.js b/test_napi/strings_test.js deleted file mode 100644 index 45cb133b2..000000000 --- a/test_napi/strings_test.js +++ /dev/null @@ -1,15 +0,0 @@ -// 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/test_napi/symbol_test.js b/test_napi/symbol_test.js deleted file mode 100644 index d8edec023..000000000 --- a/test_napi/symbol_test.js +++ /dev/null @@ -1,49 +0,0 @@ -// 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/test_napi/tests/napi_tests.rs b/test_napi/tests/napi_tests.rs deleted file mode 100644 index ce47b1e99..000000000 --- a/test_napi/tests/napi_tests.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::process::Command; -use test_util::deno_cmd; -use test_util::env_vars_for_npm_tests; -use test_util::http_server; - -#[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(test_util::napi_tests_path()) - .env("RUST_BACKTRACE", "1") - .arg("test") - .arg("--allow-read") - .arg("--allow-env") - .arg("--allow-ffi") - .arg("--allow-run") - .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/test_napi/typedarray_test.js b/test_napi/typedarray_test.js deleted file mode 100644 index 25729754a..000000000 --- a/test_napi/typedarray_test.js +++ /dev/null @@ -1,39 +0,0 @@ -// 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]), -// ); -// }); diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 7bb99a573..5eef6cf9a 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -87,8 +87,16 @@ pub fn third_party_path() -> PathRef { root_path().join("third_party") } +pub fn ffi_tests_path() -> PathRef { + root_path().join("tests").join("ffi") +} + pub fn napi_tests_path() -> PathRef { - root_path().join("test_napi") + root_path().join("tests").join("napi") +} + +pub fn deno_config_path() -> PathRef { + root_path().join("tests").join("config").join("deno.json") } /// Test server registry url. 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) { + 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 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 *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 = None; +static mut STORED_FUNCTION_2: Option u8> = None; + +#[no_mangle] +pub extern "C" fn store_function(func: Option) { + unsafe { STORED_FUNCTION = func }; + if func.is_none() { + println!("STORED_FUNCTION cleared"); + } +} + +#[no_mangle] +pub extern "C" fn store_function_2(func: Option 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; + +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 + remote.symbols.method1(0, true); + 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. + 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); + 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 = (() => G extends T ? 1 : 2) extends + (() => G extends U ? 1 : 2) ? true + : false; + +type AssertEqual< + Expected extends $, + Got extends $$, + $ = [Equal] extends [true] ? Expected + : ([Expected] extends [Got] ? never : Got), + $$ = [Equal] extends [true] ? Got + : ([Got] extends [Expected] ? never : Got), +> = never; + +type AssertNotEqual< + Expected extends $, + Got, + $ = [Equal] extends [true] ? never : Expected, +> = never; + +const enum FooEnum { + Foo, + Bar, +} +const foo = "u8" as Deno.NativeU8Enum; + +declare const brand: unique symbol; +class MyPointerClass {} +type MyPointer = Deno.PointerObject & { [brand]: MyPointerClass }; +const myPointer = "pointer" as Deno.NativeTypedPointer; +type MyFunctionDefinition = Deno.UnsafeCallbackDefinition< + [typeof foo, "u32"], + typeof myPointer +>; +const myFunction = "function" as Deno.NativeTypedFunction< + MyFunctionDefinition +>; + +type __Tests__ = [ + empty: AssertEqual< + { symbols: Record; close(): void }, + Deno.DynamicLibrary> + >, + 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 | null) => void; + }; + close(): void; + }, + Deno.DynamicLibrary< + { foo: { parameters: [typeof myFunction]; result: "void" } } + > + >, + typed_function_return: AssertEqual< + { + symbols: { + foo: () => Deno.PointerObject | 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->Data()` method also used to similarly return null pointers for all +// zero-sized buffers which would not match what `Local->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 = 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 = 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 = 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 = 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, +} + +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 = 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 = 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 = 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]), +// ); +// }); -- cgit v1.2.3