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.
This commit is contained in:
LongYinan 2023-11-07 01:46:43 +08:00 committed by GitHub
parent ca18bbdae0
commit 36581336c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 80 additions and 106 deletions

View file

@ -513,11 +513,12 @@ jobs:
- name: Build - name: Build
run: | run: |
yarn build 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 - name: Test
run: yarn workspace @examples/napi test -s run: yarn workspace @examples/napi test -s
env: env:
WASI_TEST: 'true' WASI_TEST: 'true'
NODE_OPTIONS: '--max-old-space-size=8192'
test-latest-bun: test-latest-bun:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -18,5 +18,8 @@ pub fn setup() {
println!("cargo:rustc-link-arg=--import-undefined"); println!("cargo:rustc-link-arg=--import-undefined");
println!("cargo:rustc-link-arg=--shared-memory"); println!("cargo:rustc-link-arg=--shared-memory");
println!("cargo:rustc-link-arg=--max-memory=2147483648"); 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"); println!("cargo:rustc-link-arg=--no-check-features");
} }

View file

@ -70,7 +70,7 @@ impl FromNapiValue for AbortSignal {
sys::napi_wrap( sys::napi_wrap(
env, env,
signal.0.value, 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), Some(async_task_abort_controller_finalize),
ptr::null_mut(), ptr::null_mut(),
ptr::null_mut(), ptr::null_mut(),

View file

@ -144,7 +144,11 @@ pub fn execute_tokio_future<
spawn(inner); spawn(inner);
#[cfg(target_os = "wasi")] #[cfg(target_os = "wasi")]
block_on(inner); {
std::thread::spawn(|| {
block_on(inner);
});
}
Ok(promise.0.value) Ok(promise.0.value)
} }

View file

@ -1,9 +1,7 @@
import ava from 'ava' import test from 'ava'
const { Fib, Fib2, Fib3 } = (await import('../index.js')).default const { Fib, Fib2, Fib3 } = (await import('../index.js')).default
const test = process.env.WASI_TEST ? ava.skip : ava
for (const [index, factory] of [ for (const [index, factory] of [
() => new Fib(), () => new Fib(),
() => Fib2.create(0), () => Fib2.create(0),

View file

@ -1,9 +1,7 @@
import ava from 'ava' import test from 'ava'
const { NotWritableClass } = (await import('../index.js')).default const { NotWritableClass } = (await import('../index.js')).default
const test = process.env.WASI_TEST ? ava.skip : ava
test('Not Writable Class', (t) => { test('Not Writable Class', (t) => {
const obj = new NotWritableClass('1') const obj = new NotWritableClass('1')
t.throws(() => { t.throws(() => {

View file

@ -124,10 +124,6 @@ test('should validate Map', (t) => {
}) })
test.only('should validate promise', async (t) => { test.only('should validate promise', async (t) => {
if (process.env.WASI_TEST) {
t.pass()
return
}
t.is( t.is(
await validatePromise( await validatePromise(
new Promise((resolve) => { new Promise((resolve) => {

View file

@ -144,7 +144,6 @@ const {
} = (await import('../index.js')).default } = (await import('../index.js')).default
const Napi4Test = Number(process.versions.napi) >= 4 ? test : test.skip const Napi4Test = Number(process.versions.napi) >= 4 ? test : test.skip
const isWasiTest = !!process.env.WASI_TEST
test('export const', (t) => { test('export const', (t) => {
t.is(DEFAULT_COST, 12) t.is(DEFAULT_COST, 12)
@ -356,10 +355,6 @@ test('return function', (t) => {
}) })
Napi4Test('callback function return Promise', async (t) => { Napi4Test('callback function return Promise', async (t) => {
if (isWasiTest) {
t.pass()
return
}
const cbSpy = spy() const cbSpy = spy()
await callbackReturnPromise<string>(() => '1', spy) await callbackReturnPromise<string>(() => '1', spy)
t.is(cbSpy.callCount, 0) 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) => { Napi4Test('callback function return Promise and spawn', async (t) => {
if (isWasiTest) {
t.pass()
return
}
const finalReturn = await callbackReturnPromiseAndSpawn((input) => const finalReturn = await callbackReturnPromiseAndSpawn((input) =>
Promise.resolve(`${input} world`), Promise.resolve(`${input} world`),
) )
@ -597,7 +588,7 @@ test('create external TypedArray', (t) => {
}) })
test('mutate TypedArray', (t) => { test('mutate TypedArray', (t) => {
if (isWasiTest) { if (process.env.WASI_TEST) {
t.pass() t.pass()
return return
} }
@ -671,7 +662,7 @@ test('receive class reference in either', (t) => {
test('receive different class', (t) => { test('receive different class', (t) => {
// TODO: fix the napi_unwrap error from the emnapi // TODO: fix the napi_unwrap error from the emnapi
if (isWasiTest) { if (process.env.WASI_TEST) {
t.pass() t.pass()
return return
} }
@ -863,10 +854,6 @@ Napi4Test('throw error from thread safe function fatal mode', (t) => {
}) })
Napi4Test('await Promise in rust', async (t) => { Napi4Test('await Promise in rust', async (t) => {
if (isWasiTest) {
t.pass()
return
}
const fx = 20 const fx = 20
const result = await asyncPlus100( const result = await asyncPlus100(
new Promise((resolve) => { new Promise((resolve) => {
@ -877,10 +864,6 @@ Napi4Test('await Promise in rust', async (t) => {
}) })
Napi4Test('Promise should reject raw error 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 fxError = new Error('What is Happy Planet')
const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError))) const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError)))
t.is(err, fxError) t.is(err, fxError)
@ -899,10 +882,6 @@ Napi4Test('call ThreadsafeFunction with callback', async (t) => {
}) })
Napi4Test('async call ThreadsafeFunction', async (t) => { Napi4Test('async call ThreadsafeFunction', async (t) => {
if (isWasiTest) {
t.pass()
return
}
await t.notThrowsAsync(() => await t.notThrowsAsync(() =>
tsfnAsyncCall((err, arg1, arg2, arg3) => { tsfnAsyncCall((err, arg1, arg2, arg3) => {
t.is(err, null) t.is(err, null)
@ -915,10 +894,6 @@ Napi4Test('async call ThreadsafeFunction', async (t) => {
}) })
test('Throw from ThreadsafeFunction JavaScript callback', async (t) => { test('Throw from ThreadsafeFunction JavaScript callback', async (t) => {
if (isWasiTest) {
t.pass()
return
}
const errMsg = 'ThrowFromJavaScriptRawCallback' const errMsg = 'ThrowFromJavaScriptRawCallback'
await t.throwsAsync( 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) => { Napi4Test('threadsafe function return Promise and await in Rust', async (t) => {
if (isWasiTest) {
t.pass()
return
}
const value = await tsfnReturnPromise((err, value) => { const value = await tsfnReturnPromise((err, value) => {
if (err) { if (err) {
throw err throw err
@ -1016,10 +987,6 @@ Napi4Test('object only from js', (t) => {
}) })
Napi4Test('promise in either', async (t) => { Napi4Test('promise in either', async (t) => {
if (isWasiTest) {
t.pass()
return
}
t.is(await promiseInEither(1), false) t.is(await promiseInEither(1), false)
t.is(await promiseInEither(20), true) t.is(await promiseInEither(20), true)
t.is(await promiseInEither(Promise.resolve(1)), false) t.is(await promiseInEither(Promise.resolve(1)), false)
@ -1073,7 +1040,7 @@ Napi9Test('create symbol for', (t) => {
}) })
Napi9Test('get module file name', (t) => { Napi9Test('get module file name', (t) => {
if (isWasiTest) { if (process.env.WASI_TEST) {
t.pass() t.pass()
return return
} }

View file

@ -10,23 +10,22 @@ const __dirname = join(fileURLToPath(import.meta.url), '..')
const t = const t =
// aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout // aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout
(process.arch === 'arm64' && process.platform === 'linux') || process.arch === 'arm64' && process.platform === 'linux' ? test.skip : test
process.env.WASI_TEST
? test.skip
: test
const concurrency = const concurrency = process.env.WASI_TEST
process.platform === 'win32' || ? 1
process.platform === 'darwin' || : process.platform === 'win32' ||
(process.platform === 'linux' && process.arch === 'x64') process.platform === 'darwin' ||
? 50 (process.platform === 'linux' && process.arch === 'x64')
: 10 ? 50
: 10
t('should be able to require in worker thread', async (t) => { t('should be able to require in worker thread', async (t) => {
await Promise.all( await Promise.all(
Array.from({ length: concurrency }).map(() => { Array.from({ length: concurrency }).map(() => {
const w = new Worker(join(__dirname, 'worker.cjs'), { const w = new Worker(join(__dirname, 'worker.cjs'), {
execArgv: [], execArgv: ['--experimental-wasi-unstable-preview1'],
env: process.env,
}) })
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
w.postMessage({ type: 'require' }) w.postMessage({ type: 'require' })
@ -52,7 +51,8 @@ t('custom GC works on worker_threads', async (t) => {
Promise.all([ Promise.all([
new Promise<Worker>((resolve, reject) => { new Promise<Worker>((resolve, reject) => {
const w = new Worker(join(__dirname, 'worker.cjs'), { const w = new Worker(join(__dirname, 'worker.cjs'), {
execArgv: [], execArgv: ['--experimental-wasi-unstable-preview1'],
env: process.env,
}) })
w.postMessage({ w.postMessage({
type: 'async:buffer', type: 'async:buffer',
@ -93,7 +93,8 @@ t('should be able to new Class in worker thread concurrently', async (t) => {
await Promise.all( await Promise.all(
Array.from({ length: concurrency }).map(() => { Array.from({ length: concurrency }).map(() => {
const w = new Worker(join(__dirname, 'worker.cjs'), { const w = new Worker(join(__dirname, 'worker.cjs'), {
execArgv: [], execArgv: ['--experimental-wasi-unstable-preview1'],
env: process.env,
}) })
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
w.postMessage({ type: 'constructor' }) w.postMessage({ type: 'constructor' })

View file

@ -1,49 +1,55 @@
const { parentPort } = require('worker_threads') const { parentPort } = require('worker_threads')
const native = require('../index.node') const isWasiTest = !!process.env.WASI_TEST
parentPort.on('message', ({ type }) => { import('../index.js').then(({ default: native }) => {
switch (type) { parentPort.on('message', ({ type }) => {
case 'require': switch (type) {
parentPort.postMessage( case 'require':
native.Animal.withKind(native.Kind.Cat).whoami() + native.DEFAULT_COST, parentPort.postMessage(
) native.Animal.withKind(native.Kind.Cat).whoami() + native.DEFAULT_COST,
break )
case 'async:buffer': break
Promise.all( case 'async:buffer':
Array.from({ length: 100 }).map(() => Promise.all(
native.bufferPassThrough(Buffer.from([1, 2, 3])), Array.from({ length: isWasiTest ? 2 : 100 }).map(() =>
), native.bufferPassThrough(Buffer.from([1, 2, 3])),
) ),
.then(() => { )
parentPort.postMessage('done') .then(() => {
}) parentPort.postMessage('done')
.catch((e) => { })
throw e .catch((e) => {
}) throw e
break })
case 'async:arraybuffer': break
Promise.all( case 'async:arraybuffer':
Array.from({ length: 100 }).map(() => Promise.all(
native.arrayBufferPassThrough(Uint8Array.from([1, 2, 3])), Array.from({ length: isWasiTest ? 2 : 100 }).map(() =>
), native.arrayBufferPassThrough(Uint8Array.from([1, 2, 3])),
) ),
.then(() => { )
parentPort.postMessage('done') .then(() => {
}) parentPort.postMessage('done')
.catch((e) => { })
throw e .catch((e) => {
}) throw e
})
break break
case 'constructor': case 'constructor':
let ellie let ellie
for (let i = 0; i < 10000; i++) { for (let i = 0; i < (isWasiTest ? 10 : 10000); i++) {
ellie = new native.Animal(native.Kind.Cat, 'Ellie') ellie = new native.Animal(native.Kind.Cat, 'Ellie')
} }
parentPort.postMessage(ellie.name) parentPort.postMessage(ellie.name)
break break
default: default:
throw new TypeError(`Unknown message type: ${type}`) throw new TypeError(`Unknown message type: ${type}`)
} }
})
}).catch((e) => {
console.error(e)
process.exit(1)
}) })