Skip to content

Commit 5541a3c

Browse files
authored
Merge pull request #14 from thibaudmichaud/tests
Add JS Promise Integration tests
2 parents 5556d9a + ef3f806 commit 5541a3c

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// META: global=jsshell
2+
// META: script=/wasm/jsapi/wasm-module-builder.js
3+
4+
function ToPromising(wasm_export) {
5+
let sig = WebAssembly.Function.type(wasm_export);
6+
assert_true(sig.parameters.length > 0);
7+
assert_equals('externref', sig.parameters[0]);
8+
let wrapper_sig = {
9+
parameters: sig.parameters.slice(1),
10+
results: ['externref']
11+
};
12+
return new WebAssembly.Function(
13+
wrapper_sig, wasm_export, {promising: 'first'});
14+
}
15+
16+
test(() => {
17+
let builder = new WasmModuleBuilder();
18+
let sig_i_ri = makeSig([kWasmAnyRef, kWasmI32], [kWasmI32]);
19+
let sig_v_ri = makeSig([kWasmAnyRef, kWasmI32], []);
20+
builder.addImport('m', 'import', sig_v_ri);
21+
builder.addFunction("export", sig_i_ri)
22+
.addBody([kExprLocalGet, 1]).exportFunc();
23+
builder.addFunction("void_export", kSig_v_r).addBody([]).exportFunc();
24+
function js_import(i) {}
25+
26+
// Wrap the import, instantiate the module, and wrap the export.
27+
let import_wrapper = new WebAssembly.Function(
28+
{parameters: ['externref', 'i32'], results: []},
29+
js_import,
30+
{suspending: 'first'});
31+
let instance = builder.instantiate({'m': {'import': import_wrapper}});
32+
let export_wrapper = ToPromising(instance.exports.export);
33+
34+
// Bad flag value.
35+
assert_throws(TypeError, () => new WebAssembly.Function(
36+
{parameters: ['externref', 'i32'], results: []},
37+
js_import,
38+
{suspending: 'foo'}));
39+
40+
assert_throws(TypeError, () => new WebAssembly.Function(
41+
{parameters: ['i32'], results: ['externref']},
42+
instance.exports.export,
43+
{promising: 'foo'}));
44+
45+
// Signature mismatch.
46+
assert_throws(TypeError, () => new WebAssembly.Function(
47+
{parameters: ['externref'], results: []},
48+
new WebAssembly.Function(
49+
{parameters: [], results: ['i32']}, js_import),
50+
{suspending: 'first'}));
51+
52+
assert_throws(TypeError, () => new WebAssembly.Function(
53+
{parameters: ['externref', 'i32'], results: ['i32']},
54+
instance.exports.export,
55+
{promising: 'first'}));
56+
57+
// Check the wrapper signatures.
58+
let export_sig = WebAssembly.Function.type(export_wrapper);
59+
assert_array_equals(['i32'], export_sig.parameters);
60+
assert_array_equals(['externref'], export_sig.results);
61+
62+
let import_sig = WebAssembly.Function.type(import_wrapper);
63+
assert_array_equals(['externref', 'i32'], import_sig.parameters);
64+
assert_array_equals([], import_sig.results);
65+
66+
let void_export_wrapper = ToPromising(instance.exports.void_export);
67+
let void_export_sig = WebAssembly.Function.type(void_export_wrapper);
68+
assert_array_equals([], void_export_sig.parameters);
69+
assert_array_equals(['externref'], void_export_sig.results);
70+
}, "Test import and export type checking");
71+
72+
promise_test(async () => {
73+
let builder = new WasmModuleBuilder();
74+
import_index = builder.addImport('m', 'import', kSig_i_r);
75+
builder.addFunction("test", kSig_i_r)
76+
.addBody([
77+
kExprLocalGet, 0,
78+
kExprCallFunction, import_index, // suspend
79+
]).exportFunc();
80+
let js_import = new WebAssembly.Function(
81+
{parameters: ['externref'], results: ['i32']},
82+
() => Promise.resolve(42),
83+
{suspending: 'first'});
84+
let instance = builder.instantiate({m: {import: js_import}});
85+
let wrapped_export = ToPromising(instance.exports.test);
86+
let export_promise = wrapped_export();
87+
assert_true(export_promise instanceof Promise);
88+
assert_equals(42, await export_promise);
89+
}, "Suspend once");
90+
91+
promise_test(async () => {
92+
let builder = new WasmModuleBuilder();
93+
builder.addGlobal(kWasmI32, true).exportAs('g');
94+
import_index = builder.addImport('m', 'import', kSig_i_r);
95+
// void test() {
96+
// for (i = 0; i < 5; ++i) {
97+
// g = g + await import();
98+
// }
99+
// }
100+
builder.addFunction("test", kSig_v_r)
101+
.addLocals({ i32_count: 1})
102+
.addBody([
103+
kExprI32Const, 5,
104+
kExprLocalSet, 1,
105+
kExprLoop, kWasmStmt,
106+
kExprLocalGet, 0,
107+
kExprCallFunction, import_index, // suspend
108+
kExprGlobalGet, 0,
109+
kExprI32Add,
110+
kExprGlobalSet, 0,
111+
kExprLocalGet, 1,
112+
kExprI32Const, 1,
113+
kExprI32Sub,
114+
kExprLocalTee, 1,
115+
kExprBrIf, 0,
116+
kExprEnd,
117+
]).exportFunc();
118+
let i = 0;
119+
function js_import() {
120+
return Promise.resolve(++i);
121+
};
122+
let wasm_js_import = new WebAssembly.Function(
123+
{parameters: ['externref'], results: ['i32']},
124+
js_import,
125+
{suspending: 'first'});
126+
let instance = builder.instantiate({m: {import: wasm_js_import}});
127+
let wrapped_export = ToPromising(instance.exports.test);
128+
let export_promise = wrapped_export();
129+
assert_equals(0, instance.exports.g.value);
130+
assert_true(export_promise instanceof Promise);
131+
await export_promise;
132+
assert_equals(15, instance.exports.g.value);
133+
}, "Suspend/resume in a loop");
134+
135+
test(() => {
136+
let builder = new WasmModuleBuilder();
137+
import_index = builder.addImport('m', 'import', kSig_i_r);
138+
builder.addFunction("test", kSig_i_r)
139+
.addBody([
140+
kExprLocalGet, 0,
141+
kExprCallFunction, import_index, // suspend
142+
]).exportFunc();
143+
function js_import() {
144+
return 42
145+
};
146+
let wasm_js_import = new WebAssembly.Function(
147+
{parameters: ['externref'], results: ['i32']},
148+
js_import,
149+
{suspending: 'first'});
150+
let instance = builder.instantiate({m: {import: wasm_js_import}});
151+
let wrapped_export = ToPromising(instance.exports.test);
152+
assert_equals(42, wrapped_export());
153+
}, "Do not suspend if the import's return value is not a Promise");
154+
155+
test(t => {
156+
let tag = new WebAssembly.Tag({parameters: []});
157+
let builder = new WasmModuleBuilder();
158+
import_index = builder.addImport('m', 'import', kSig_i_r);
159+
tag_index = builder.addImportedException('m', 'tag', kSig_v_v);
160+
builder.addFunction("test", kSig_i_r)
161+
.addBody([
162+
kExprLocalGet, 0,
163+
kExprCallFunction, import_index,
164+
kExprThrow, tag_index
165+
]).exportFunc();
166+
function js_import() {
167+
return Promise.resolve();
168+
};
169+
let wasm_js_import = new WebAssembly.Function(
170+
{parameters: ['externref'], results: ['i32']},
171+
js_import,
172+
{suspending: 'first'});
173+
174+
let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}});
175+
let wrapped_export = ToPromising(instance.exports.test);
176+
let export_promise = wrapped_export();
177+
assert_true(export_promise instanceof Promise);
178+
promise_rejects(t, new WebAssembly.Exception(tag, []), export_promise);
179+
}, "Throw after the first suspension");
180+
181+
promise_test(async () => {
182+
let tag = new WebAssembly.Tag({parameters: ['i32']});
183+
let builder = new WasmModuleBuilder();
184+
import_index = builder.addImport('m', 'import', kSig_i_r);
185+
tag_index = builder.addImportedException('m', 'tag', kSig_v_i);
186+
builder.addFunction("test", kSig_i_r)
187+
.addBody([
188+
kExprTry, kWasmI32,
189+
kExprLocalGet, 0,
190+
kExprCallFunction, import_index,
191+
kExprCatch, tag_index,
192+
kExprEnd,
193+
]).exportFunc();
194+
function js_import() {
195+
return Promise.reject(new WebAssembly.Exception(tag, [42]));
196+
};
197+
let wasm_js_import = new WebAssembly.Function(
198+
{parameters: ['externref'], results: ['i32']},
199+
js_import,
200+
{suspending: 'first'});
201+
202+
let instance = builder.instantiate({m: {import: wasm_js_import, tag: tag}});
203+
let wrapped_export = ToPromising(instance.exports.test);
204+
let export_promise = wrapped_export();
205+
assert_true(export_promise instanceof Promise);
206+
assert_equals(42, await export_promise);
207+
}, "Rejecting promise");
208+
209+
async function TestNestedSuspenders(suspend) {
210+
// Nest two suspenders. The call chain looks like:
211+
// outer (wasm) -> outer (js) -> inner (wasm) -> inner (js)
212+
// If 'suspend' is true, the inner JS function returns a Promise, which
213+
// suspends the inner wasm function, which returns a Promise, which suspends
214+
// the outer wasm function, which returns a Promise. The inner Promise
215+
// resolves first, which resumes the inner continuation. Then the outer
216+
// promise resolves which resumes the outer continuation.
217+
// If 'suspend' is false, the inner JS function returns a regular value and
218+
// no computation is suspended.
219+
let builder = new WasmModuleBuilder();
220+
inner_index = builder.addImport('m', 'inner', kSig_i_r);
221+
outer_index = builder.addImport('m', 'outer', kSig_i_r);
222+
builder.addFunction("outer", kSig_i_r)
223+
.addBody([
224+
kExprLocalGet, 0,
225+
kExprCallFunction, outer_index
226+
]).exportFunc();
227+
builder.addFunction("inner", kSig_i_r)
228+
.addBody([
229+
kExprLocalGet, 0,
230+
kExprCallFunction, inner_index
231+
]).exportFunc();
232+
233+
let inner = new WebAssembly.Function(
234+
{parameters: ['externref'], results: ['i32']},
235+
() => suspend ? Promise.resolve(42) : 43,
236+
{suspending: 'first'});
237+
238+
let export_inner;
239+
let outer = new WebAssembly.Function(
240+
{parameters: ['externref'], results: ['i32']},
241+
() => export_inner(),
242+
{suspending: 'first'});
243+
244+
let instance = builder.instantiate({m: {inner, outer}});
245+
export_inner = ToPromising(instance.exports.inner);
246+
let export_outer = ToPromising(instance.exports.outer);
247+
let result = export_outer();
248+
if (suspend) {
249+
assert_true(result instanceof Promise);
250+
assert_equals(42, await result);
251+
} else {
252+
assert_equals(43, result);
253+
}
254+
}
255+
256+
test(() => {
257+
TestNestedSuspenders(true);
258+
}, "Test nested suspenders with suspension");
259+
260+
test(() => {
261+
TestNestedSuspenders(false);
262+
}, "Test nested suspenders with no suspension");
263+
264+
test(() => {
265+
let builder = new WasmModuleBuilder();
266+
let import_index = builder.addImport('m', 'import', kSig_i_r);
267+
builder.addFunction("test", kSig_i_r)
268+
.addBody([
269+
kExprLocalGet, 0,
270+
kExprCallFunction, import_index, // suspend
271+
]).exportFunc();
272+
builder.addFunction("return_suspender", kSig_r_r)
273+
.addBody([
274+
kExprLocalGet, 0
275+
]).exportFunc();
276+
let js_import = new WebAssembly.Function(
277+
{parameters: ['externref'], results: ['i32']},
278+
() => Promise.resolve(42),
279+
{suspending: 'first'});
280+
let instance = builder.instantiate({m: {import: js_import}});
281+
let suspender = ToPromising(instance.exports.return_suspender)();
282+
for (s of [suspender, null, undefined, {}]) {
283+
assert_throws(WebAssembly.RuntimeError, () => instance.exports.test(s));
284+
}
285+
}, "Call import with an invalid suspender");

0 commit comments

Comments
 (0)