From b98c45ca5a46966bce1ac691d5f608beb36d5db7 Mon Sep 17 00:00:00 2001
From: Maya Lekova <apokalyptra@gmail.com>
Date: Wed, 17 Oct 2018 22:10:09 +0200
Subject: [PATCH] AsyncFunction: Add tests ensuring the new 1-tick await
 behaviour (#1843)

* AsyncFunction: Add tests ensuring the new 1-tick await behaviour

This commit adds 3 tests ensuring the optimized behaviour of await
(see https://github.com/tc39/ecma262/pull/1250) in the following cases:
- async functions
- yielding from async generator functions
- for-await-of loops

* AsyncFunction: Add tests ensuring the monkey-patched promises behaviour

This commit adds 2 more tests ensuring the optimized behaviour of await
(see tc39/ecma262#1250) in the following cases:
- awaiting on a native promise with monkey-patched "then"
- awaiting on a non-native promise (a "thenable" object)

* AsyncFunction: Add tests ensuring the non-native promises behaviour

This commit adds 1 more tests ensuring the optimized behaviour of await
(see tc39/ecma262#1250) in the following cases:
- awaiting on a non-promise, non-thenable object

It also renames the previous test for non-promise (a "thenable" object)
to distinguish from the new case.

The commit adds checks for proper await/promises interleaving in the
aforementioned cases and includes a small code clean-up.

* AsyncFunction: Refactor tests ensuring the new 1-tick await behaviour

Gather all the tests to their appropriate folder and update copyright header.
---
 .../await/async-await-interleaved.js          | 43 ++++++++++++++
 .../await/async-generator-interleaved.js      | 41 +++++++++++++
 .../await/await-monkey-patched-promise.js     | 49 ++++++++++++++++
 .../await/await-non-promise-thenable.js       | 55 ++++++++++++++++++
 .../expressions/await/await-non-promise.js    | 43 ++++++++++++++
 .../await/for-await-of-interleaved.js         | 57 +++++++++++++++++++
 6 files changed, 288 insertions(+)
 create mode 100644 test/language/expressions/await/async-await-interleaved.js
 create mode 100644 test/language/expressions/await/async-generator-interleaved.js
 create mode 100644 test/language/expressions/await/await-monkey-patched-promise.js
 create mode 100644 test/language/expressions/await/await-non-promise-thenable.js
 create mode 100644 test/language/expressions/await/await-non-promise.js
 create mode 100644 test/language/expressions/await/for-await-of-interleaved.js

diff --git a/test/language/expressions/await/async-await-interleaved.js b/test/language/expressions/await/async-await-interleaved.js
new file mode 100644
index 0000000000..33cde9f685
--- /dev/null
+++ b/test/language/expressions/await/async-await-interleaved.js
@@ -0,0 +1,43 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  Await on async functions and builtin Promises are properly interleaved,
+  meaning await takes only 1 tick on the microtask queue.
+flags: [async]
+features: [async-functions]
+---*/
+
+const actual = [];
+const expected = [
+  'Await: 1',
+  'Promise: 1',
+  'Await: 2',
+  'Promise: 2'
+];
+
+async function pushAwait(value) {
+  actual.push('Await: ' + value);
+}
+
+async function callAsync() {
+  await pushAwait(1);
+  await pushAwait(2);
+}
+
+function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+}
+
+callAsync();
+
+new Promise(function (resolve) {
+  actual.push('Promise: 1');
+  resolve();
+}).then(function () {
+  actual.push('Promise: 2');
+}).then(checkAssertions).then($DONE, $DONE);
diff --git a/test/language/expressions/await/async-generator-interleaved.js b/test/language/expressions/await/async-generator-interleaved.js
new file mode 100644
index 0000000000..fdc3076eed
--- /dev/null
+++ b/test/language/expressions/await/async-generator-interleaved.js
@@ -0,0 +1,41 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  Await on async generator functions and builtin Promises are properly
+  interleaved, meaning await takes only 1 tick on the microtask queue.
+flags: [async]
+features: [async-functions]
+---*/
+
+const actual = [];
+const expected = [ 'await', 1, 'await', 2 ];
+const iterations = 2;
+
+async function pushAwait() {
+  actual.push('await');
+}
+
+async function* callAsync() {
+  for (let i = 0; i < iterations; i++) {
+    await pushAwait();
+  }
+  return 0;
+}
+
+function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+}
+
+callAsync().next();
+
+new Promise(function (resolve) {
+  actual.push(1);
+  resolve();
+}).then(function () {
+  actual.push(2);
+}).then(checkAssertions).then($DONE, $DONE);
diff --git a/test/language/expressions/await/await-monkey-patched-promise.js b/test/language/expressions/await/await-monkey-patched-promise.js
new file mode 100644
index 0000000000..ff2bebe6ea
--- /dev/null
+++ b/test/language/expressions/await/await-monkey-patched-promise.js
@@ -0,0 +1,49 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  This test demonstrates that monkey-patched "then" on native promises will
+  not get called. Adapted from example by Kevin Smith:
+  https://github.com/tc39/ecma262/pull/1250#issuecomment-401082195
+flags: [async]
+features: [async-functions]
+---*/
+
+let thenCallCount = 0;
+const value = 42;
+
+const actual = [];
+const expected = [
+  'Promise: 1',
+  'Await: ' + value,
+  'Promise: 2',
+];
+
+const patched = Promise.resolve(value);
+patched.then = function(...args) {
+  thenCallCount++;
+  Promise.prototype.then.apply(this, args);
+};
+
+async function trigger() {
+  actual.push('Await: ' + await patched);
+}
+
+function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+  assert.sameValue(thenCallCount, 0,
+    'Monkey-patched "then" on native promises should not be called.')
+}
+
+trigger().then(checkAssertions).then($DONE, $DONE);
+
+new Promise(function (resolve) {
+  actual.push('Promise: 1');
+  resolve();
+}).then(function () {
+  actual.push('Promise: 2');
+});
diff --git a/test/language/expressions/await/await-non-promise-thenable.js b/test/language/expressions/await/await-non-promise-thenable.js
new file mode 100644
index 0000000000..0efa2d688c
--- /dev/null
+++ b/test/language/expressions/await/await-non-promise-thenable.js
@@ -0,0 +1,55 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  This test demonstrates that "then" on a non-native promise
+  will still get called.
+flags: [async]
+features: [async-functions]
+---*/
+
+let thenCallCount = 0;
+
+const actual = [];
+const expected = [
+  'Promise: 1',
+  'Promise: 2',
+  'Await: 1',
+  'Promise: 3',
+  'Promise: 4',
+  'Await: 2',
+];
+
+const patched = {};
+patched.then = function(fulfill, reject) {
+  thenCallCount++;
+  fulfill(thenCallCount);
+};
+
+async function trigger() {
+  actual.push('Await: ' + await patched);
+  actual.push('Await: ' + await patched);
+}
+
+function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+  assert.sameValue(thenCallCount, 2,
+    '"then" on non-native promises should be called.');
+}
+
+trigger().then(checkAssertions).then($DONE, $DONE);
+
+new Promise(function (resolve) {
+  actual.push('Promise: 1');
+  resolve();
+}).then(function () {
+  actual.push('Promise: 2');
+}).then(function () {
+  actual.push('Promise: 3');
+}).then(function () {
+  actual.push('Promise: 4');
+});
diff --git a/test/language/expressions/await/await-non-promise.js b/test/language/expressions/await/await-non-promise.js
new file mode 100644
index 0000000000..dadef94fcb
--- /dev/null
+++ b/test/language/expressions/await/await-non-promise.js
@@ -0,0 +1,43 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  This test demonstrates that "then" on a non-native promise
+  will still get called.
+flags: [async]
+features: [async-functions]
+---*/
+
+const value = 1;
+
+const actual = [];
+const expected = [
+  'Await: 1',
+  'Promise: 1',
+  'Promise: 2',
+];
+
+function pushAwaitSync(value) {
+  actual.push('Await: ' + value);
+}
+
+async function trigger() {
+  await pushAwaitSync(value);
+}
+
+function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+}
+
+trigger().then(checkAssertions).then($DONE, $DONE);
+
+new Promise(function (resolve) {
+  actual.push('Promise: 1');
+  resolve();
+}).then(function () {
+  actual.push('Promise: 2');
+});
diff --git a/test/language/expressions/await/for-await-of-interleaved.js b/test/language/expressions/await/for-await-of-interleaved.js
new file mode 100644
index 0000000000..f8cf07d70a
--- /dev/null
+++ b/test/language/expressions/await/for-await-of-interleaved.js
@@ -0,0 +1,57 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+author: Maya Lekova <mslekova@chromium.org>
+esid: await
+description: >
+  for-await-of iteration and builtin Promises are properly interleaved,
+  meaning await in for-of loop takes only 1 tick on the microtask queue.
+flags: [async]
+features: [async-functions, async-iteration]
+---*/
+
+const actual = [];
+const expected = [
+  'Promise: 6',
+  'Promise: 5',
+  'Await: 3',
+  'Promise: 4',
+  'Promise: 3',
+  'Await: 2',
+  'Promise: 2',
+  'Promise: 1',
+  'Await: 1',
+  'Promise: 0'
+];
+const iterations = 3;
+
+async function* naturalNumbers(start) {
+  let current = start;
+  while (current > 0) {
+    yield Promise.resolve(current--);
+  }
+}
+
+async function trigger() {
+  for await (const num of naturalNumbers(iterations)) {
+    actual.push('Await: ' + num);
+  }
+}
+
+async function checkAssertions() {
+  assert.compareArray(actual, expected,
+    'Async/await and promises should be interleaved');
+}
+
+function countdown(counter) {
+  actual.push('Promise: ' + counter);
+  if (counter > 0) {
+    return Promise.resolve(counter - 1).then(countdown);
+  } else {
+    checkAssertions().then($DONE, $DONE);
+  }
+}
+
+trigger();
+countdown(iterations * 2);
-- 
GitLab