| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Flags: --allow-natives-syntax |
| |
| function assertEqualsAsync(expected, run, msg) { |
| var actual; |
| var hadValue = false; |
| var hadError = false; |
| var promise = run(); |
| |
| if (typeof promise !== "object" || typeof promise.then !== "function") { |
| throw new MjsUnitAssertionError( |
| "Expected " + run.toString() + |
| " to return a Promise, but it returned " + PrettyPrint(promise)); |
| } |
| |
| promise.then(function(value) { hadValue = true; actual = value; }, |
| function(error) { hadError = true; actual = error; }); |
| |
| assertFalse(hadValue || hadError); |
| |
| %PerformMicrotaskCheckpoint(); |
| |
| if (hadError) throw actual; |
| |
| assertTrue( |
| hadValue, "Expected '" + run.toString() + "' to produce a value"); |
| |
| assertEquals(expected, actual, msg); |
| }; |
| |
| // Rename a function so that it can help omit things from stack trace. |
| function test(fn) { |
| return Object.defineProperty(fn, "name", { |
| enumerable: false, |
| configurable: true, |
| value: "@" + fn.name, |
| writable: false |
| }); |
| } |
| |
| function getStack(error) { |
| var stack = error.stack.split('\n'). |
| filter(function(line) { |
| return /^\s*at @?[a-zA-Z0-9_]/.test(line); |
| }). |
| map(line => |
| line.replace(/^\s*at (@?(?:new )?[a-zA-Z0-9_\.\[\]]+)(.*)/, "$1")); |
| |
| // remove `Promise.then()` invocation by assertEqualsAsync() |
| if (stack[2] === "assertEqualsAsync") return []; |
| |
| return stack.reverse(); |
| } |
| |
| var log = []; |
| class FakePromise extends Promise { |
| constructor(executor) { |
| var stack = getStack(new Error("Getting Callstack")); |
| if (stack.length) { |
| var first = -1; |
| for (var i = 0; i < stack.length; ++i) { |
| if (stack[i][0] === '@') { |
| first = i; |
| break; |
| } |
| } |
| while (first > 0) stack.shift(), --first; |
| if (stack.length) { |
| log.push("@@Species: [" + stack.join(" > ") + "]"); |
| } |
| } |
| return new Promise(executor); |
| } |
| }; |
| |
| Object.defineProperty(Promise, Symbol.species, { |
| value: FakePromise, |
| configurable: true, |
| enumerable: false, |
| writable: false |
| }); |
| |
| // Internal `AsyncFunctionAwait` only --- no @@species invocations. |
| async function asyncFn() { return await "foo"; } |
| assertEqualsAsync("foo", test(function testInternalOnly() { return asyncFn(); }, |
| "should not call Promise[@@Species]")); |
| assertEquals([], log); |
| |
| log.length = 0; |
| assertEqualsAsync( |
| "foo", |
| test(function testThenOnReturnedPromise() { |
| return asyncFn().then(x => (log.push("Then: " + x), x)); |
| }), |
| "should call Promise[@@Species] after non-internal Then"); |
| assertEquals([ |
| "@@Species: [@testThenOnReturnedPromise > Promise.then > new FakePromise]", |
| "Then: foo" |
| ], log); |