From 36581336c696812fe36883650a4839e186563f0a Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 7 Nov 2023 01:46:43 +0800 Subject: [PATCH] feat(napi): pass the rest of async tests (#1792) Pass the rest of async tests, including await the JavaScript Promise in the Rust side, and the worker_threads tests. --- .github/workflows/test-release.yaml | 3 +- crates/build/src/wasi.rs | 3 + .../src/bindgen_runtime/js_values/task.rs | 2 +- crates/napi/src/tokio_runtime.rs | 6 +- examples/napi/__tests__/generator.spec.ts | 4 +- examples/napi/__tests__/object-attr.spec.ts | 4 +- examples/napi/__tests__/strict.spec.ts | 4 - examples/napi/__tests__/values.spec.ts | 39 +------- examples/napi/__tests__/worker-thread.spec.ts | 27 +++--- examples/napi/__tests__/worker.cjs | 94 ++++++++++--------- 10 files changed, 80 insertions(+), 106 deletions(-) diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index 5a800cd5..a3e3a0f9 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -513,11 +513,12 @@ jobs: - name: Build run: | yarn build - yarn workspace @examples/napi build --target wasm32-wasi-preview1-threads + yarn workspace @examples/napi build --target wasm32-wasi-preview1-threads --release - name: Test run: yarn workspace @examples/napi test -s env: WASI_TEST: 'true' + NODE_OPTIONS: '--max-old-space-size=8192' test-latest-bun: runs-on: ubuntu-latest diff --git a/crates/build/src/wasi.rs b/crates/build/src/wasi.rs index 242ff395..77b389d2 100644 --- a/crates/build/src/wasi.rs +++ b/crates/build/src/wasi.rs @@ -18,5 +18,8 @@ pub fn setup() { println!("cargo:rustc-link-arg=--import-undefined"); println!("cargo:rustc-link-arg=--shared-memory"); println!("cargo:rustc-link-arg=--max-memory=2147483648"); + // lld only allocates 1MiB for the WebAssembly stack, and the array that you're allocating on the stack is exactly 1MiB. + // 0x800000 bytes = 8MiB + println!("cargo:rustc-link-arg=-zstack-size=0x800000"); println!("cargo:rustc-link-arg=--no-check-features"); } diff --git a/crates/napi/src/bindgen_runtime/js_values/task.rs b/crates/napi/src/bindgen_runtime/js_values/task.rs index 11b551a8..f4facfa5 100644 --- a/crates/napi/src/bindgen_runtime/js_values/task.rs +++ b/crates/napi/src/bindgen_runtime/js_values/task.rs @@ -70,7 +70,7 @@ impl FromNapiValue for AbortSignal { sys::napi_wrap( env, signal.0.value, - Box::into_raw(Box::new(abort_controller)) as *mut _, + Box::into_raw(Box::new(abort_controller)).cast(), Some(async_task_abort_controller_finalize), ptr::null_mut(), ptr::null_mut(), diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index e76dd0bf..02fc96d9 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -144,7 +144,11 @@ pub fn execute_tokio_future< spawn(inner); #[cfg(target_os = "wasi")] - block_on(inner); + { + std::thread::spawn(|| { + block_on(inner); + }); + } Ok(promise.0.value) } diff --git a/examples/napi/__tests__/generator.spec.ts b/examples/napi/__tests__/generator.spec.ts index 97efbf4a..cbbcc3cd 100644 --- a/examples/napi/__tests__/generator.spec.ts +++ b/examples/napi/__tests__/generator.spec.ts @@ -1,9 +1,7 @@ -import ava from 'ava' +import test from 'ava' const { Fib, Fib2, Fib3 } = (await import('../index.js')).default -const test = process.env.WASI_TEST ? ava.skip : ava - for (const [index, factory] of [ () => new Fib(), () => Fib2.create(0), diff --git a/examples/napi/__tests__/object-attr.spec.ts b/examples/napi/__tests__/object-attr.spec.ts index 99d06590..5a2ebc0d 100644 --- a/examples/napi/__tests__/object-attr.spec.ts +++ b/examples/napi/__tests__/object-attr.spec.ts @@ -1,9 +1,7 @@ -import ava from 'ava' +import test from 'ava' const { NotWritableClass } = (await import('../index.js')).default -const test = process.env.WASI_TEST ? ava.skip : ava - test('Not Writable Class', (t) => { const obj = new NotWritableClass('1') t.throws(() => { diff --git a/examples/napi/__tests__/strict.spec.ts b/examples/napi/__tests__/strict.spec.ts index b62a17f9..5e8acbf2 100644 --- a/examples/napi/__tests__/strict.spec.ts +++ b/examples/napi/__tests__/strict.spec.ts @@ -124,10 +124,6 @@ test('should validate Map', (t) => { }) test.only('should validate promise', async (t) => { - if (process.env.WASI_TEST) { - t.pass() - return - } t.is( await validatePromise( new Promise((resolve) => { diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 7595ed24..4eb3865b 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -144,7 +144,6 @@ const { } = (await import('../index.js')).default const Napi4Test = Number(process.versions.napi) >= 4 ? test : test.skip -const isWasiTest = !!process.env.WASI_TEST test('export const', (t) => { t.is(DEFAULT_COST, 12) @@ -356,10 +355,6 @@ test('return function', (t) => { }) Napi4Test('callback function return Promise', async (t) => { - if (isWasiTest) { - t.pass() - return - } const cbSpy = spy() await callbackReturnPromise(() => '1', spy) t.is(cbSpy.callCount, 0) @@ -375,10 +370,6 @@ Napi4Test('callback function return Promise', async (t) => { }) Napi4Test('callback function return Promise and spawn', async (t) => { - if (isWasiTest) { - t.pass() - return - } const finalReturn = await callbackReturnPromiseAndSpawn((input) => Promise.resolve(`${input} world`), ) @@ -597,7 +588,7 @@ test('create external TypedArray', (t) => { }) test('mutate TypedArray', (t) => { - if (isWasiTest) { + if (process.env.WASI_TEST) { t.pass() return } @@ -671,7 +662,7 @@ test('receive class reference in either', (t) => { test('receive different class', (t) => { // TODO: fix the napi_unwrap error from the emnapi - if (isWasiTest) { + if (process.env.WASI_TEST) { t.pass() return } @@ -863,10 +854,6 @@ Napi4Test('throw error from thread safe function fatal mode', (t) => { }) Napi4Test('await Promise in rust', async (t) => { - if (isWasiTest) { - t.pass() - return - } const fx = 20 const result = await asyncPlus100( new Promise((resolve) => { @@ -877,10 +864,6 @@ Napi4Test('await Promise in rust', async (t) => { }) Napi4Test('Promise should reject raw error in rust', async (t) => { - if (isWasiTest) { - t.pass() - return - } const fxError = new Error('What is Happy Planet') const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError))) t.is(err, fxError) @@ -899,10 +882,6 @@ Napi4Test('call ThreadsafeFunction with callback', async (t) => { }) Napi4Test('async call ThreadsafeFunction', async (t) => { - if (isWasiTest) { - t.pass() - return - } await t.notThrowsAsync(() => tsfnAsyncCall((err, arg1, arg2, arg3) => { t.is(err, null) @@ -915,10 +894,6 @@ Napi4Test('async call ThreadsafeFunction', async (t) => { }) test('Throw from ThreadsafeFunction JavaScript callback', async (t) => { - if (isWasiTest) { - t.pass() - return - } const errMsg = 'ThrowFromJavaScriptRawCallback' await t.throwsAsync( () => @@ -968,10 +943,6 @@ Napi4Test('accept ThreadsafeFunction tuple args', async (t) => { }) Napi4Test('threadsafe function return Promise and await in Rust', async (t) => { - if (isWasiTest) { - t.pass() - return - } const value = await tsfnReturnPromise((err, value) => { if (err) { throw err @@ -1016,10 +987,6 @@ Napi4Test('object only from js', (t) => { }) Napi4Test('promise in either', async (t) => { - if (isWasiTest) { - t.pass() - return - } t.is(await promiseInEither(1), false) t.is(await promiseInEither(20), true) t.is(await promiseInEither(Promise.resolve(1)), false) @@ -1073,7 +1040,7 @@ Napi9Test('create symbol for', (t) => { }) Napi9Test('get module file name', (t) => { - if (isWasiTest) { + if (process.env.WASI_TEST) { t.pass() return } diff --git a/examples/napi/__tests__/worker-thread.spec.ts b/examples/napi/__tests__/worker-thread.spec.ts index f5ec786d..6a238c63 100644 --- a/examples/napi/__tests__/worker-thread.spec.ts +++ b/examples/napi/__tests__/worker-thread.spec.ts @@ -10,23 +10,22 @@ const __dirname = join(fileURLToPath(import.meta.url), '..') const t = // aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout - (process.arch === 'arm64' && process.platform === 'linux') || - process.env.WASI_TEST - ? test.skip - : test + process.arch === 'arm64' && process.platform === 'linux' ? test.skip : test -const concurrency = - process.platform === 'win32' || - process.platform === 'darwin' || - (process.platform === 'linux' && process.arch === 'x64') - ? 50 - : 10 +const concurrency = process.env.WASI_TEST + ? 1 + : process.platform === 'win32' || + process.platform === 'darwin' || + (process.platform === 'linux' && process.arch === 'x64') + ? 50 + : 10 t('should be able to require in worker thread', async (t) => { await Promise.all( Array.from({ length: concurrency }).map(() => { const w = new Worker(join(__dirname, 'worker.cjs'), { - execArgv: [], + execArgv: ['--experimental-wasi-unstable-preview1'], + env: process.env, }) return new Promise((resolve, reject) => { w.postMessage({ type: 'require' }) @@ -52,7 +51,8 @@ t('custom GC works on worker_threads', async (t) => { Promise.all([ new Promise((resolve, reject) => { const w = new Worker(join(__dirname, 'worker.cjs'), { - execArgv: [], + execArgv: ['--experimental-wasi-unstable-preview1'], + env: process.env, }) w.postMessage({ type: 'async:buffer', @@ -93,7 +93,8 @@ t('should be able to new Class in worker thread concurrently', async (t) => { await Promise.all( Array.from({ length: concurrency }).map(() => { const w = new Worker(join(__dirname, 'worker.cjs'), { - execArgv: [], + execArgv: ['--experimental-wasi-unstable-preview1'], + env: process.env, }) return new Promise((resolve, reject) => { w.postMessage({ type: 'constructor' }) diff --git a/examples/napi/__tests__/worker.cjs b/examples/napi/__tests__/worker.cjs index 036595a4..2fd25686 100644 --- a/examples/napi/__tests__/worker.cjs +++ b/examples/napi/__tests__/worker.cjs @@ -1,49 +1,55 @@ const { parentPort } = require('worker_threads') -const native = require('../index.node') +const isWasiTest = !!process.env.WASI_TEST -parentPort.on('message', ({ type }) => { - switch (type) { - case 'require': - parentPort.postMessage( - native.Animal.withKind(native.Kind.Cat).whoami() + native.DEFAULT_COST, - ) - break - case 'async:buffer': - Promise.all( - Array.from({ length: 100 }).map(() => - native.bufferPassThrough(Buffer.from([1, 2, 3])), - ), - ) - .then(() => { - parentPort.postMessage('done') - }) - .catch((e) => { - throw e - }) - break - case 'async:arraybuffer': - Promise.all( - Array.from({ length: 100 }).map(() => - native.arrayBufferPassThrough(Uint8Array.from([1, 2, 3])), - ), - ) - .then(() => { - parentPort.postMessage('done') - }) - .catch((e) => { - throw e - }) +import('../index.js').then(({ default: native }) => { + parentPort.on('message', ({ type }) => { + switch (type) { + case 'require': + parentPort.postMessage( + native.Animal.withKind(native.Kind.Cat).whoami() + native.DEFAULT_COST, + ) + break + case 'async:buffer': + Promise.all( + Array.from({ length: isWasiTest ? 2 : 100 }).map(() => + native.bufferPassThrough(Buffer.from([1, 2, 3])), + ), + ) + .then(() => { + parentPort.postMessage('done') + }) + .catch((e) => { + throw e + }) + break + case 'async:arraybuffer': + Promise.all( + Array.from({ length: isWasiTest ? 2 : 100 }).map(() => + native.arrayBufferPassThrough(Uint8Array.from([1, 2, 3])), + ), + ) + .then(() => { + parentPort.postMessage('done') + }) + .catch((e) => { + throw e + }) - break - case 'constructor': - let ellie - for (let i = 0; i < 10000; i++) { - ellie = new native.Animal(native.Kind.Cat, 'Ellie') - } - parentPort.postMessage(ellie.name) - break - default: - throw new TypeError(`Unknown message type: ${type}`) - } + break + case 'constructor': + let ellie + for (let i = 0; i < (isWasiTest ? 10 : 10000); i++) { + ellie = new native.Animal(native.Kind.Cat, 'Ellie') + } + parentPort.postMessage(ellie.name) + break + default: + throw new TypeError(`Unknown message type: ${type}`) + } + }) +}).catch((e) => { + console.error(e) + process.exit(1) }) +