js-promise-integration.new.js (16975B)
1 // JS promise integration API tests 2 // Modified https://github.com/WebAssembly/js-promise-integration/tree/main/test/js-api/js-promise-integration 3 4 var tests = Promise.resolve(); 5 function test(fn, n) { 6 if (n == null) n = (new Error()).stack.split('\n')[1]; 7 tests = tests.then(() => { 8 let t = {res: null}; 9 print("# " + n); 10 fn(t); 11 return t.res; 12 }, (e) => { 13 throw e; 14 }); 15 } 16 function promise_test(fn, n) { 17 if (n == null) n = (new Error()).stack.split('\n')[1]; 18 tests = tests.then(() => { 19 let t = {res: null}; 20 print("# " + n); 21 return fn(t); 22 }); 23 } 24 function assert_true(f) { assertEq(f, true); } 25 function assert_false(f) { assertEq(f, false); } 26 function assert_equals(a, b) { assertEq(a, b); } 27 function assert_array_equals(a, b) { 28 assert_equals(a.length, a.length); 29 for (let i = 0; i < a.length; i++) { 30 assert_equals(a[i], b[i]); 31 } 32 } 33 function assert_throws(ex, fn, msg) { 34 try { 35 fn(); 36 } catch(e) { 37 assertEq(e instanceof ex, true, `Type of e: ${e.constructor.name}`); 38 return; 39 } 40 assertEq(false, true, "fail expected"); 41 } 42 function promise_rejects(t, obj, p) { 43 t.res = p.then(() => { 44 assertEq(true, false); 45 }, (e) => { 46 assertEq(e instanceof obj.constructor, true); 47 }); 48 } 49 50 function Promising(wasm_export) { 51 return WebAssembly.promising(wasm_export); 52 } 53 54 function Suspending(jsFun){ 55 return new WebAssembly.Suspending(jsFun); 56 } 57 58 // Test for invalid wrappers 59 test(() => { 60 assert_throws(TypeError, () => WebAssembly.promising({}), 61 /Argument 0 must be a function/); 62 assert_throws(TypeError, () => WebAssembly.promising(() => {}), 63 /Argument 0 must be a WebAssembly exported function/); 64 assert_throws(TypeError, () => WebAssembly.Suspending(() => {}), 65 /WebAssembly.Suspending must be invoked with 'new'/); 66 assert_throws(TypeError, () => new WebAssembly.Suspending({}), 67 /Argument 0 must be a function/); 68 function asmModule() { 69 "use asm"; 70 function x(v) { 71 v = v | 0; 72 } 73 return x; 74 } 75 assert_throws(TypeError, () => WebAssembly.promising(asmModule()), 76 /Argument 0 must be a WebAssembly exported function/); 77 }); 78 79 test(() => { 80 let instance = wasmEvalText(`(module 81 (type (func (result i32))) 82 (func $test (type 0) (result i32) 83 i32.const 42 84 global.set 0 85 i32.const 0 86 ) 87 (global (mut i32) i32.const 0) 88 (export "g" (global 0)) 89 (export "test" (func $test)) 90 )`); 91 let wrapper = WebAssembly.promising(instance.exports.test); 92 wrapper(); 93 assert_equals(42, instance.exports.g.value); 94 }); 95 96 promise_test(async () => { 97 let js_import = Suspending(() => Promise.resolve(42)); 98 let instance = wasmEvalText(`(module 99 (type (func (param i32) (result i32))) 100 (type (func (param i32) (result i32))) 101 (import "m" "import" (func (type 0))) 102 (func $test (type 1) (param i32) (result i32) 103 local.get 0 104 call 0 105 ) 106 (export "test" (func $test)) 107 )`, {m: {import: js_import}}); 108 let wrapped_export = Promising(instance.exports.test); 109 let export_promise = wrapped_export(); 110 assert_true(export_promise instanceof Promise); 111 assert_equals(await export_promise, 42); 112 }, "Suspend once"); 113 114 promise_test(async () => { 115 let i = 0; 116 function js_import() { 117 return Promise.resolve(++i); 118 }; 119 let wasm_js_import = Suspending(js_import); 120 let instance = wasmEvalText(`(module 121 (type (func (param i32) (result i32))) 122 (type (func (param i32))) 123 (import "m" "import" (func (type 0))) 124 (func $test (type 1) (param i32) 125 (local i32) 126 i32.const 5 127 local.set 1 128 loop ;; label = @1 129 local.get 0 130 call 0 131 global.get 0 132 i32.add 133 global.set 0 134 local.get 1 135 i32.const 1 136 i32.sub 137 local.tee 1 138 br_if 0 (;@1;) 139 end 140 ) 141 (global (mut i32) i32.const 0) 142 (export "g" (global 0)) 143 (export "test" (func $test)) 144 )`, {m: {import: wasm_js_import}}); 145 let wrapped_export = Promising(instance.exports.test); 146 let export_promise = wrapped_export(); 147 assert_equals(instance.exports.g.value, 0); 148 assert_true(export_promise instanceof Promise); 149 await export_promise; 150 assert_equals(instance.exports.g.value, 15); 151 }, "Suspend/resume in a loop"); 152 153 promise_test(async ()=>{ 154 let js_import = new WebAssembly.Suspending(() => Promise.resolve(42)); 155 let instance = wasmEvalText(`(module 156 (type (func (result i32))) 157 (type (func (result i32))) 158 (import "m" "import" (func (type 0))) 159 (func $test (type 1) (result i32) 160 call 0 161 ) 162 (export "test" (func $test)) 163 )`, {m: {import: js_import}}); 164 let wrapped_export = WebAssembly.promising(instance.exports.test); 165 assert_equals(await wrapped_export(), 42); 166 167 // Also try with a JS function with a mismatching arity. 168 js_import = new WebAssembly.Suspending((unused) => Promise.resolve(42)); 169 instance = wasmEvalText(`(module 170 (type (func (result i32))) 171 (type (func (result i32))) 172 (import "m" "import" (func (type 0))) 173 (func $test (type 1) (result i32) 174 call 0 175 ) 176 (export "test" (func $test)) 177 )`, {m: {import: js_import}}); 178 wrapped_export = WebAssembly.promising(instance.exports.test); 179 assert_equals(await wrapped_export(), 42); 180 181 // Also try with a proxy. 182 js_import = new WebAssembly.Suspending(new Proxy(() => Promise.resolve(42), {})); 183 instance = wasmEvalText(`(module 184 (type (func (result i32))) 185 (type (func (result i32))) 186 (import "m" "import" (func (type 0))) 187 (func $test (type 1) (result i32) 188 call 0 189 ) 190 (export "test" (func $test)) 191 )`, {m: {import: js_import}}); 192 wrapped_export = WebAssembly.promising(instance.exports.test); 193 assert_equals(await wrapped_export(), 42); 194 }); 195 196 function recordAbeforeB(){ 197 let AbeforeB = []; 198 let setA = ()=>{ 199 AbeforeB.push("A") 200 } 201 let setB = ()=>{ 202 AbeforeB.push("B") 203 } 204 let isAbeforeB = ()=> 205 AbeforeB[0]=="A" && AbeforeB[1]=="B"; 206 207 let showAbeforeB = ()=>{ 208 console.log(AbeforeB) 209 } 210 return {setA : setA, setB : setB, isAbeforeB :isAbeforeB,showAbeforeB:showAbeforeB} 211 } 212 213 promise_test(async () => { 214 let AbeforeB = recordAbeforeB(); 215 let import42 = Suspending(()=>Promise.resolve(42)); 216 let instance = wasmEvalText(`(module 217 (type (func (param i32) (result i32))) 218 (type (func)) 219 (type (func (param i32) (result i32))) 220 (import "m" "import42" (func (type 0))) 221 (import "m" "setA" (func (type 1))) 222 (func $test (type 2) (param i32) (result i32) 223 local.get 0 224 call 0 225 call 1 226 ) 227 (export "test" (func $test)) 228 )`, {m: {import42: import42, setA:AbeforeB.setA}}); 229 230 let wrapped_export = Promising(instance.exports.test); 231 232 // AbeforeB.showAbeforeB(); 233 let exported_promise = wrapped_export(); 234 // AbeforeB.showAbeforeB(); 235 236 AbeforeB.setB(); 237 238 //print(await exported_promise); 239 assert_equals(await exported_promise, 42); 240 // AbeforeB.showAbeforeB(); 241 242 assert_false(AbeforeB.isAbeforeB()); 243 }, "Make sure we actually suspend"); 244 245 promise_test(async () => { 246 let AbeforeB = recordAbeforeB(); 247 let import42 = Suspending(()=>42); 248 let instance = wasmEvalText(`(module 249 (type (func (param i32) (result i32))) 250 (type (func)) 251 (type (func (param i32) (result i32))) 252 (import "m" "import42" (func (type 0))) 253 (import "m" "setA" (func (type 1))) 254 (func $test (type 2) (param i32) (result i32) 255 local.get 0 256 call 0 257 call 1 258 ) 259 (export "test" (func $test)) 260 )`, { 261 m: {import42: import42, setA:AbeforeB.setA}}); 262 263 let wrapped_export = Promising(instance.exports.test); 264 265 let exported_promise = wrapped_export(); 266 AbeforeB.setB(); 267 268 assert_equals(await exported_promise, 42); 269 // AbeforeB.showAbeforeB(); 270 271 assert_false(AbeforeB.isAbeforeB()); 272 }, "Suspend if the import's return value is not a Promise"); 273 274 test(t => { 275 console.log("Throw after the first suspension"); 276 let tag = new WebAssembly.Tag({parameters: []}); 277 function js_import() { 278 return Promise.resolve(); 279 }; 280 let wasm_js_import = Suspending(js_import); 281 282 let instance = wasmEvalText(`(module 283 (type (func (param i32) (result i32))) 284 (type (func)) 285 (type (func (param i32) (result i32))) 286 (import "m" "import" (func (type 0))) 287 (import "m" "tag" (tag (type 1))) 288 (func $test (type 2) (param i32) (result i32) 289 local.get 0 290 call 0 291 throw 0 292 ) 293 (export "test" (func $test)) 294 )`, {m: {import: wasm_js_import, tag: tag}}); 295 let wrapped_export = Promising(instance.exports.test); 296 let export_promise = wrapped_export(); 297 assert_true(export_promise instanceof Promise); 298 promise_rejects(t, new WebAssembly.Exception(tag, []), export_promise); 299 }, "Throw after the first suspension"); 300 301 promise_test(async (t) => { 302 console.log("Rejecting promise"); 303 let tag = new WebAssembly.Tag({parameters: ['i32']}); 304 function js_import() { 305 return Promise.reject(new WebAssembly.Exception(tag, [42])); 306 }; 307 let wasm_js_import = Suspending(js_import); 308 309 let instance = wasmEvalText(`(module 310 (type (func (param i32) (result i32))) 311 (type (func (param i32))) 312 (type (func (param i32) (result i32))) 313 (import "m" "import" (func (type 0))) 314 (import "m" "tag" (tag (type 1) (param i32))) 315 (func $test (type 2) (param i32) (result i32) 316 try (result i32) ;; label = @1 317 local.get 0 318 call 0 319 catch 0 320 end 321 ) 322 (export "test" (func $test)) 323 )`, {m: {import: wasm_js_import, tag: tag}}); 324 let wrapped_export = Promising(instance.exports.test); 325 let export_promise = wrapped_export(); 326 assert_true(export_promise instanceof Promise); 327 assert_equals(await export_promise, 42); 328 }, "Rejecting promise"); 329 330 async function TestNestedSuspenders(suspend) { 331 console.log("nested suspending "+suspend); 332 // Nest two suspenders. The call chain looks like: 333 // outer (wasm) -> outer (js) -> inner (wasm) -> inner (js) 334 // If 'suspend' is true, the inner JS function returns a Promise, which 335 // suspends the inner wasm function, which returns a Promise, which suspends 336 // the outer wasm function, which returns a Promise. The inner Promise 337 // resolves first, which resumes the inner continuation. Then the outer 338 // promise resolves which resumes the outer continuation. 339 // If 'suspend' is false, the inner JS function returns a regular value and 340 // no computation is suspended. 341 342 let inner = Suspending(() => suspend ? Promise.resolve(42) : 43); 343 344 let export_inner; 345 let outer = Suspending(() => export_inner()); 346 347 let instance = wasmEvalText(`(module 348 (type (func (param i32) (result i32))) 349 (type (func (param i32) (result i32))) 350 (type (func (param i32) (result i32))) 351 (type (func (param i32) (result i32))) 352 (import "m" "inner" (func (type 0))) 353 (import "m" "outer" (func (type 1))) 354 (func $outer (type 2) (param i32) (result i32) 355 local.get 0 356 call 1 357 ) 358 (func $inner (type 3) (param i32) (result i32) 359 local.get 0 360 call 0 361 ) 362 (export "outer" (func $outer)) 363 (export "inner" (func $inner)) 364 )`, {m: {inner, outer}}); 365 export_inner = Promising(instance.exports.inner); 366 let export_outer = Promising(instance.exports.outer); 367 let result = export_outer(); 368 assert_true(result instanceof Promise); 369 if(suspend) 370 assert_equals(await result, 42); 371 else 372 assert_equals(await result, 43); 373 } 374 375 promise_test(async () => { 376 TestNestedSuspenders(true); 377 }, "Test nested suspenders with suspension"); 378 379 promise_test(async () => { 380 TestNestedSuspenders(false); 381 }, "Test nested suspenders with no suspension"); 382 383 test(() => { 384 console.log("Call import with an invalid suspender"); 385 let js_import = Suspending(() => Promise.resolve(42)); 386 let instance = wasmEvalText(`(module 387 (type (func (param i32) (result i32))) 388 (type (func (param i32) (result i32))) 389 (type (func (param i32) (result i32))) 390 (import "m" "import" (func (type 0))) 391 (func $test (type 1) (param i32) (result i32) 392 local.get 0 393 call 0 394 ) 395 (func $return_suspender (type 2) (param i32) (result i32) 396 local.get 0 397 ) 398 (export "test" (func $test)) 399 (export "return_suspender" (func $return_suspender)) 400 )`, {m: {import: js_import}}); 401 let suspender = Promising(instance.exports.return_suspender)(); 402 for (s of [suspender, null, undefined, {}]) { 403 assert_throws(WebAssembly.SuspendError, () => instance.exports.test(s)); 404 } 405 }, "Call import with an invalid suspender"); 406 407 // Throw an exception before suspending. The export wrapper should return a 408 // promise rejected with the exception. 409 promise_test(async (t) => { 410 let tag = new WebAssembly.Tag({parameters: []}); 411 412 let instance = wasmEvalText(`(module 413 (type (func)) 414 (type (func (result i32))) 415 (import "m" "tag" (tag (type 0))) 416 (func $test (type 1) (result i32) 417 throw 0 418 ) 419 (export "test" (func $test)) 420 )`, {m: {tag: tag}}); 421 let wrapped_export = WebAssembly.promising(instance.exports.test); 422 let export_promise = wrapped_export(); 423 424 promise_rejects(t, new WebAssembly.Exception(tag, []), export_promise); 425 }); 426 427 // Throw an exception after the first resume event, which propagates to the 428 // promise wrapper. 429 promise_test(async (t) => { 430 let tag = new WebAssembly.Tag({parameters: []}); 431 function js_import() { 432 return Promise.resolve(42); 433 }; 434 let wasm_js_import = new WebAssembly.Suspending(js_import); 435 436 let instance = wasmEvalText(`(module 437 (type (func (result i32))) 438 (type (func)) 439 (type (func (result i32))) 440 (import "m" "import" (func (type 0))) 441 (import "m" "tag" (tag (type 1))) 442 (func $test (type 2) (result i32) 443 call 0 444 throw 0 445 ) 446 (export "test" (func $test)) 447 )`, {m: {import: wasm_js_import, tag: tag}}); 448 let wrapped_export = WebAssembly.promising(instance.exports.test); 449 let export_promise = wrapped_export(); 450 451 promise_rejects(t, new WebAssembly.Exception(tag, []), export_promise); 452 }); 453 454 promise_test(async () => { 455 let tag = new WebAssembly.Tag({parameters: ['i32']}); 456 function js_import() { 457 return Promise.reject(new WebAssembly.Exception(tag, [42])); 458 }; 459 let wasm_js_import = new WebAssembly.Suspending(js_import); 460 461 let instance = wasmEvalText(`(module 462 (type (func (result i32))) 463 (type (func (param i32))) 464 (type (func (result i32))) 465 (import "m" "import" (func (type 0))) 466 (import "m" "tag" (tag (type 1) (param i32))) 467 (func $test (type 2) (result i32) 468 try (result i32) ;; label = @1 469 call 0 470 catch 0 471 end 472 ) 473 (export "test" (func $test)) 474 )`, {m: {import: wasm_js_import, tag: tag}}); 475 let wrapped_export = WebAssembly.promising(instance.exports.test); 476 assert_equals(await wrapped_export(), 42); 477 }); 478 479 test(() => { 480 console.log("no return allowed"); 481 // Check that a promising function with no return is allowed. 482 let instance = wasmEvalText(`(module 483 (type (func)) 484 (func $export (type 0)) 485 (export "export" (func $export)) 486 )`); 487 let export_wrapper = WebAssembly.promising(instance.exports.export); 488 assert_true(export_wrapper instanceof Function); 489 }, "wrapper type"); 490 491 promise_test(async (t) => { 492 let instance = wasmEvalText(`(module 493 (type (func (result i32))) 494 (func $test (type 0) (result i32) 495 call $test 496 ) 497 (export "test" (func $test)) 498 )`); 499 let wrapper = WebAssembly.promising(instance.exports.test); 500 501 promise_rejects(t, new Error(), wrapper(), /Maximum call stack size exceeded/); 502 }); 503 504 promise_test(async (t) => { 505 // The call stack of this test looks like: 506 // export1 -> import1 -> export2 -> import2 507 // Where export1 is "promising" and import2 is "suspending". Returning a 508 // promise from import2 should trap because of the JS import in the middle. 509 let instance; 510 function import1() { 511 // import1 -> export2 (unwrapped) 512 instance.exports.export2(); 513 } 514 function import2() { 515 return Promise.resolve(0); 516 } 517 import2 = new WebAssembly.Suspending(import2); 518 instance = wasmEvalText(`(module 519 (type (func (result i32))) 520 (type (func (result i32))) 521 (type (func (result i32))) 522 (type (func (result i32))) 523 (import "m" "import1" (func (type 0))) 524 (import "m" "import2" (func (type 1))) 525 (func $export1 (type 2) (result i32) 526 call 0 527 ) 528 (func $export2 (type 3) (result i32) 529 call 1 530 ) 531 (export "export1" (func $export1)) 532 (export "export2" (func $export2)) 533 )`, 534 {'m': 535 {'import1': import1, 536 'import2': import2 537 }}); 538 // export1 (promising) 539 let wrapper = WebAssembly.promising(instance.exports.export1); 540 promise_rejects(t, new WebAssembly.SuspendError(), wrapper(), 541 /trying to suspend JS frames/); 542 }); 543 544 promise_test(async () => { 545 let js_import = new WebAssembly.Suspending(() => Promise.resolve(1)); 546 let instance1 = wasmEvalText(`(module 547 (type (func (result i32))) 548 (type (func (result i32))) 549 (import "m" "import" (func (type 0))) 550 (func $f (type 1) (result i32) 551 call 0 552 i32.const 1 553 i32.add 554 ) 555 (export "f" (func $f)) 556 )`, {m: {import: js_import}}); 557 let instance2 = wasmEvalText(`(module 558 (type (func (result i32))) 559 (type (func (result i32))) 560 (import "m" "import" (func (type 0))) 561 (func $main (type 1) (result i32) 562 call 0 563 i32.const 1 564 i32.add 565 ) 566 (export "main" (func $main)) 567 )`, {m: {import: instance1.exports.f}}); 568 let wrapped_export = WebAssembly.promising(instance2.exports.main); 569 assert_equals(await wrapped_export(), 3); 570 }); 571 572 tests.then(() => print('Done'));