From 9e38acde952fd424f27cf9c923b19cda19ff08dd Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Wed, 3 Apr 2024 10:32:39 -0400 Subject: [PATCH] chore(neon): Add tests for neon::export --- test/napi/lib/export.js | 96 ++++++++++++++++++++++++++++++++++++++ test/napi/src/js/export.rs | 90 +++++++++++++++++++++++++++++++++++ test/napi/src/lib.rs | 5 ++ 3 files changed, 191 insertions(+) create mode 100644 test/napi/lib/export.js create mode 100644 test/napi/src/js/export.rs diff --git a/test/napi/lib/export.js b/test/napi/lib/export.js new file mode 100644 index 000000000..a0f6141e7 --- /dev/null +++ b/test/napi/lib/export.js @@ -0,0 +1,96 @@ +const assert = require("assert"); + +const addon = require(".."); + +describe("neon::export macro", () => { + describe("globals", globals); + describe("functions", functions); +}); + +function globals() { + it("values", () => { + assert.strictEqual(addon.NUMBER, 42); + assert.strictEqual(addon.STRING, "Hello, World!"); + assert.strictEqual(addon.renamedString, "Hello, World!"); + }); + + it("json", () => { + assert.deepStrictEqual(addon.MESSAGES, ["hello", "neon"]); + assert.deepStrictEqual(addon.renamedMessages, ["hello", "neon"]); + }); +} + +function functions() { + it("void function", () => { + assert.strictEqual(addon.no_args_or_return(), undefined); + }); + + it("add - sync", () => { + assert.strictEqual(addon.simple_add(1, 2), 3); + assert.strictEqual(addon.renamedAdd(1, 2), 3); + }); + + it("add - task", async () => { + const p1 = addon.add_task(1, 2); + const p2 = addon.renamedAddTask(1, 2); + + assert.ok(p1 instanceof Promise); + assert.ok(p2 instanceof Promise); + + assert.strictEqual(await p1, 3); + assert.strictEqual(await p2, 3); + }); + + it("json sort", () => { + const arr = ["b", "c", "a"]; + const expected = [...arr].sort(); + + assert.deepStrictEqual(addon.json_sort(arr), expected); + assert.deepStrictEqual(addon.renamedJsonSort(arr), expected); + }); + + it("json sort - task", async () => { + const arr = ["b", "c", "a"]; + const expected = [...arr].sort(); + const p1 = addon.json_sort_task(arr); + const p2 = addon.renamedJsonSortTask(arr); + + assert.ok(p1 instanceof Promise); + assert.ok(p2 instanceof Promise); + + assert.deepStrictEqual(await p1, expected); + assert.deepStrictEqual(await p2, expected); + }); + + it("can use context and handles", () => { + const actual = addon.concat_with_cx_and_handle("Hello,", " World!"); + const expected = "Hello, World!"; + + assert.strictEqual(actual, expected); + }); + + it("error conversion", () => { + const msg = "Oh, no!"; + const expected = new Error(msg); + + assert.throws(() => addon.fail_with_throw(msg), expected); + }); + + it("tasks are concurrent", async () => { + const time = 100; + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + const start = process.hrtime.bigint(); + + await Promise.all([addon.sleep_task(time), sleep(time)]); + + const end = process.hrtime.bigint(); + const duration = end - start; + + // If `addon.sleep_task` blocks the thread, the tasks will run sequentially + // and take a minimum of 2x `time`. Since they are run concurrently, we + // expect the time to be closer to 1x `time`. + const maxExpected = 2000000n * BigInt(time); + + assert.ok(duration < maxExpected); + }); +} diff --git a/test/napi/src/js/export.rs b/test/napi/src/js/export.rs new file mode 100644 index 000000000..160ef1f62 --- /dev/null +++ b/test/napi/src/js/export.rs @@ -0,0 +1,90 @@ +use neon::{prelude::*, types::extract::Error}; + +#[neon::export] +const NUMBER: u8 = 42; + +#[neon::export] +static STRING: &str = "Hello, World!"; + +#[neon::export(name = "renamedString")] +static RENAMED_STRING: &str = STRING; + +#[neon::export(json)] +static MESSAGES: &[&str] = &["hello", "neon"]; + +#[neon::export(name = "renamedMessages", json)] +static RENAMED_MESSAGES: &[&str] = MESSAGES; + +#[neon::export] +fn no_args_or_return() {} + +#[neon::export] +fn simple_add(a: f64, b: f64) -> f64 { + a + b +} + +#[neon::export(name = "renamedAdd")] +fn renamed_add(a: f64, b: f64) -> f64 { + simple_add(a, b) +} + +#[neon::export(task)] +fn add_task(a: f64, b: f64) -> f64 { + simple_add(a, b) +} + +#[neon::export(task, name = "renamedAddTask")] +fn renamed_add_task(a: f64, b: f64) -> f64 { + add_task(a, b) +} + +#[neon::export(json)] +fn json_sort(mut items: Vec) -> Vec { + items.sort(); + items +} + +#[neon::export(json, name = "renamedJsonSort")] +fn renamed_json_sort(items: Vec) -> Vec { + json_sort(items) +} + +#[neon::export(json, task)] +fn json_sort_task(items: Vec) -> Vec { + json_sort(items) +} + +#[neon::export(json, name = "renamedJsonSortTask", task)] +fn renamed_json_sort_task(items: Vec) -> Vec { + json_sort(items) +} + +#[neon::export] +fn concat_with_cx_and_handle<'cx>( + cx: &mut FunctionContext<'cx>, + a: String, + b: Handle<'cx, JsString>, +) -> Handle<'cx, JsString> { + let b = b.value(cx); + + cx.string(a + &b) +} + +#[neon::export] +fn fail_with_throw(msg: String) -> Result<(), Error> { + fn always_fails(msg: String) -> Result<(), String> { + Err(msg) + } + + // `?` converts `String` into `Error` + always_fails(msg)?; + + Ok(()) +} + +#[neon::export(task)] +fn sleep_task(ms: f64) { + use std::{thread, time::Duration}; + + thread::sleep(Duration::from_millis(ms as u64)); +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 8131550f5..0065d5b77 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -12,6 +12,7 @@ mod js { pub mod coercions; pub mod date; pub mod errors; + pub mod export; pub mod extract; pub mod functions; pub mod futures; @@ -26,6 +27,10 @@ mod js { #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { + neon::registered().export(&mut cx)?; + + assert!(neon::registered().into_iter().next().is_some()); + let greeting = cx.string("Hello, World!"); let greeting_copy = greeting.value(&mut cx); let greeting_copy = cx.string(greeting_copy);