Skip to content

Commit

Permalink
Update and improve tests
Browse files Browse the repository at this point in the history
- Update tests that relied on the old API, where export wrappers were
  allowed to return "non-promisified" results.
- Add new tests ported from the V8 test suite
- Avoid dependency on wasm exception handling
  • Loading branch information
thibaudmichaud committed Oct 3, 2023
1 parent 48fbac8 commit 1af78dc
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 22 deletions.
146 changes: 124 additions & 22 deletions test/js-api/js-promise-integration/js-promise-integration.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,14 @@ promise_test(async () => {

test(() => {
let builder = new WasmModuleBuilder();
builder.addGlobal(kWasmI32, true).exportAs('g');
import_index = builder.addImport('m', 'import', kSig_i_r);
builder.addFunction("test", kSig_i_r)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, import_index, // suspend
kExprCallFunction, import_index,
kExprGlobalSet, 0,
kExprGlobalGet, 0,
]).exportFunc();
function js_import() {
return 42
Expand All @@ -149,19 +152,21 @@ test(() => {
{suspending: 'first'});
let instance = builder.instantiate({m: {import: wasm_js_import}});
let wrapped_export = ToPromising(instance.exports.test);
assert_equals(42, wrapped_export());
wrapped_export();
// The global was updated synchronously.
assertEquals(42, instance.exports.g.value);
}, "Do not suspend if the import's return value is not a Promise");

test(t => {
let tag = new WebAssembly.Tag({parameters: []});
let builder = new WasmModuleBuilder();
import_index = builder.addImport('m', 'import', kSig_i_r);
tag_index = builder.addImportedException('m', 'tag', kSig_v_v);
js_throw_index = builder.addImport('m', 'js_throw', kSig_v_v);
builder.addFunction("test", kSig_i_r)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, import_index,
kExprThrow, tag_index
kExprCallFunction, js_throw_index,
]).exportFunc();
function js_import() {
return Promise.resolve();
Expand All @@ -170,29 +175,30 @@ test(t => {
{parameters: ['externref'], results: ['i32']},
js_import,
{suspending: 'first'});
function js_throw() {
throw new Error();
}

let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}});
let instance = builder.instantiate({m: {import: wasm_js_import, js_throw}});
let wrapped_export = ToPromising(instance.exports.test);
let export_promise = wrapped_export();
assert_true(export_promise instanceof Promise);
promise_rejects(t, new WebAssembly.Exception(tag, []), export_promise);
promise_rejects(t, new Error(), export_promise);
}, "Throw after the first suspension");

promise_test(async () => {
// TODO: Use wasm exception handling to check that the exception can be caught in wasm.

test(t => {
let tag = new WebAssembly.Tag({parameters: ['i32']});
let builder = new WasmModuleBuilder();
import_index = builder.addImport('m', 'import', kSig_i_r);
tag_index = builder.addImportedException('m', 'tag', kSig_v_i);
builder.addFunction("test", kSig_i_r)
.addBody([
kExprTry, kWasmI32,
kExprLocalGet, 0,
kExprCallFunction, import_index,
kExprCatch, tag_index,
kExprEnd,
]).exportFunc();
function js_import() {
return Promise.reject(new WebAssembly.Exception(tag, [42]));
return Promise.reject(new Error());
};
let wasm_js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
Expand All @@ -203,7 +209,7 @@ promise_test(async () => {
let wrapped_export = ToPromising(instance.exports.test);
let export_promise = wrapped_export();
assert_true(export_promise instanceof Promise);
assert_equals(42, await export_promise);
promise_rejects(t, new Error(), export_promise);
}, "Rejecting promise");

async function TestNestedSuspenders(suspend) {
Expand All @@ -214,8 +220,8 @@ async function TestNestedSuspenders(suspend) {
// the outer wasm function, which returns a Promise. The inner Promise
// resolves first, which resumes the inner continuation. Then the outer
// promise resolves which resumes the outer continuation.
// If 'suspend' is false, the inner JS function returns a regular value and
// no computation is suspended.
// If 'suspend' is false, the inner and outer JS functions return a regular
// value and no computation is suspended.
let builder = new WasmModuleBuilder();
inner_index = builder.addImport('m', 'inner', kSig_i_r);
outer_index = builder.addImport('m', 'outer', kSig_i_r);
Expand All @@ -238,19 +244,15 @@ async function TestNestedSuspenders(suspend) {
let export_inner;
let outer = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => export_inner(),
() => suspend ? export_inner() : 42,
{suspending: 'first'});

let instance = builder.instantiate({m: {inner, outer}});
export_inner = ToPromising(instance.exports.inner);
let export_outer = ToPromising(instance.exports.outer);
let result = export_outer();
if (suspend) {
assert_true(result instanceof Promise);
assert_equals(42, await result);
} else {
assert_equals(43, result);
}
assert_true(result instanceof Promise);
assert_equals(42, await result);
}

test(() => {
Expand Down Expand Up @@ -283,3 +285,103 @@ test(() => {
assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s));
}
}, "Call import with an invalid suspender");

test(t => {
let builder = new WasmModuleBuilder();
builder.addFunction("test", kSig_i_r)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, 0
]).exportFunc();
let instance = builder.instantiate();
let wrapper = ToPromising(instance.exports.test);
promise_rejects(t, new RangeError(), wrapper());
}, "Stack overflow");

test (() => {
let builder = new WasmModuleBuilder();
let import_index = builder.addImport('m', 'import', kSig_i_r);
builder.addFunction("test", kSig_i_r)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, import_index, // suspend
]).exportFunc();
builder.addFunction("return_suspender", kSig_r_r)
.addBody([
kExprLocalGet, 0
]).exportFunc();
let js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => Promise.resolve(42),
{suspending: 'first'});
let instance = builder.instantiate({m: {import: js_import}});
let suspender = ToPromising(instance.exports.return_suspender)();
for (s of [suspender, null, undefined, {}]) {
assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s));
}
}, "Pass an invalid suspender");

// TODO: Test suspension with funcref.

test(t => {
// The call stack of this test looks like:
// export1 -> import1 -> export2 -> import2
// Where export1 is "promising" and import2 is "suspending". Returning a
// promise from import2 should trap because of the JS import in the middle.
let builder = new WasmModuleBuilder();
let import1_index = builder.addImport("m", "import1", kSig_i_v);
let import2_index = builder.addImport("m", "import2", kSig_i_r);
builder.addGlobal(kWasmAnyRef, true);
builder.addFunction("export1", kSig_i_r)
.addBody([
// export1 -> import1 (unwrapped)
kExprLocalGet, 0,
kExprGlobalSet, 0,
kExprCallFunction, import1_index,
]).exportFunc();
builder.addFunction("export2", kSig_i_v)
.addBody([
// export2 -> import2 (suspending)
kExprGlobalGet, 0,
kExprCallFunction, import2_index,
]).exportFunc();
let instance;
function import1() {
// import1 -> export2 (unwrapped)
instance.exports.export2();
}
function import2() {
return Promise.resolve(0);
}
import2 = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
import2,
{suspending: 'first'});
instance = builder.instantiate(
{'m':
{'import1': import1,
'import2': import2
}});
// export1 (promising)
let wrapper = new WebAssembly.Function(
{parameters: [], results: ['externref']},
instance.exports.export1,
{promising: 'first'});
promise_rejects(t, new WebAssembly.RuntimeError(), wrapper());
}, "Test that trying to suspend JS frames traps");

test(() => {
let builder = new WasmModuleBuilder();
import_index = builder.addImport('m', 'import', kSig_i_r);
builder.addFunction("test", kSig_i_r)
.addBody([
kExprLocalGet, 0,
kExprCallFunction, import_index, // suspend
]).exportFunc();
let js_import = new WebAssembly.Function(
{parameters: ['externref'], results: ['i32']},
() => 42,
{suspending: 'first'});
let instance = builder.instantiate({m: {import: js_import}});
assertEquals(42, instance.exports.test(null));
}, "Pass an invalid suspender to the import and return a non-promise");
1 change: 1 addition & 0 deletions test/js-api/wasm-module-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ class WasmModuleBuilder {
section.emit_u32v(global.function_index);
} else {
section.emit_u8(kExprRefNull);
section.emit_u8(kWasmAnyRef);
}
break;
}
Expand Down

0 comments on commit 1af78dc

Please sign in to comment.