// Copyright 2017 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: --harmony-async-iteration --allow-natives-syntax

let testFailed = false;
let testFailure;
(async function() {
  const kNext = 1;
  const kThrow = 2;
  const kReturn = 4;
  const kReturnPrimitive = kReturn | 32;

  function async(iterable, features = kNext, log = []) {
    // Helper to turn a synchronous iterable into an asynchronous iterable,
    // without using the [Async-from-Sync Iterator].
    let it = iterable[Symbol.iterator]();
    let methods = {
      next(sentValue) {
        return new Promise(function(resolve, reject) {
          let {value, done} = it.next(sentValue);
          Promise.resolve(value).then(function(value) {
            log.push('.next() -> resolved ' + value);
            resolve({value, done});
          }, function(value) {
            log.push('.next() -> rejected ' + value);
            reject(value);
          });
        });
      },

      throw(sentValue) {
        return new Promise(function(resolve, reject) {
          let throwMethod = it.throw;
          if (typeof throwMethod !== 'function') {
            log.push('.throw(' + sentValue + ')');
            return reject(sentValue);
          }

          let {value, done} = throwMethod.call(it, sentValue);
          Promise.resolve(value).then(function(value) {
                log.push('.throw() -> resolved ' + value);
                resolve({ value, done });
              }, function(value) {
                log.push('.throw() -> rejected ' + value);
                reject(value);
              });
        });
      },

      return(sentValue) {
        return new Promise(function(resolve, reject) {
          let returnMethod = it.return;
          if (typeof returnMethod !== 'function') {
            log.push('.return(' + sentValue + ')');
            if ((features & kReturnPrimitive) === kReturnPrimitive)
                return resolve(sentValue);
            return resolve({value: sentValue, done: true});
          }

          let {value, done} = returnMethod.call(it, sentValue);
          Promise.resolve(value).then(function(value) {
            log.push('.return() -> resolved ' + value);
            if ((features & kReturnPrimitive) === kReturnPrimitive)
                return resolve(value);
            resolve({ value, done });
          }, function(value) {
            log.push('.return() -> rejected ' + value);
            reject(value);
          });
        });
      }
    };


    return {
      [Symbol.asyncIterator]() {
        log.push('[Symbol.asyncIterator]()')
        return this;
      },

      next: (features & kNext) ? methods.next : undefined,
      throw: (features & kThrow) ? methods.throw : undefined,
      return: (features & kReturn) ? methods.return : undefined
    };
  }

  let testDone;
  let test;
  async function testBindingIdentifierVarDeclarationStatement() {
    let sum = 0;
    testDone = false;
    for await (var value of async([100, 200, 300, 400, 500])) sum += value;
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierVarDeclarationStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testBindingIdentifierVarDeclarationBlockStatement() {
    let sum = 0;
    testDone = false;
    for await (var value of async([100, 200, 300, 400, 500])) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      sum += value;
    }
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierVarDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternVarDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (var {key = 'unknown', value} of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternVarDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternVarDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (var {key = 'unknown', value} of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternVarDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternVarDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (var [key = 'unknown', value] of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternVarDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternVarDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (var [key = 'unknown', value] of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternVarDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  // --------------------------------------------------------------------------

  async function testBindingIdentifierLetDeclarationStatement() {
    let sum = 0;
    testDone = false;
    for await (let value of async([100, 200, 300, 400, 500])) sum += value;
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierLetDeclarationStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testBindingIdentifierLetDeclarationBlockStatement() {
    let sum = 0;
    testDone = false;
    for await (let value of async([100, 200, 300, 400, 500])) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      sum += value;
    }
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierLetDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternLetDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (let {key = 'unknown', value} of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternLetDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternLetDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (let {key = 'unknown', value} of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  let threwEarly = false;
  test = testObjectBindingPatternLetDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternTDZLetDeclarationStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    let value = { value: 1 };
    try {
      for await (let {value} of async([value])) sum += value;
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  test = testObjectBindingPatternTDZLetDeclarationStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testObjectBindingPatternTDZLetDeclarationBlockStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    let value = { value: 1 };
    try {
      for await (let {value} of async([value])) {
        sum += value;
      }
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testObjectBindingPatternTDZLetDeclarationBlockStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testArrayBindingPatternLetDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (let [key = 'unknown', value] of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternLetDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternLetDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (let [key = 'unknown', value] of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternLetDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternTDZLetDeclarationStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    let value = [1];
    try {
      for await (let [value] of async([value])) sum += value;
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testArrayBindingPatternTDZLetDeclarationStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testArrayBindingPatternTDZLetDeclarationBlockStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    let value = [1];
    try {
      for await (let [value] of async([value])) {
        sum += value;
      }
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testArrayBindingPatternTDZLetDeclarationBlockStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  // --------------------------------------------------------------------------

  async function testBindingIdentifierConstDeclarationStatement() {
    let sum = 0;
    testDone = false;
    for await (let value of async([100, 200, 300, 400, 500])) sum += value;
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierConstDeclarationStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testBindingIdentifierConstDeclarationBlockStatement() {
    let sum = 0;
    testDone = false;
    for await (const value of async([100, 200, 300, 400, 500])) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      sum += value;
    }
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierConstDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternConstDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (const {key = 'unknown', value} of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternConstDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternConstDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [
      {key: 'first', value: 10}, {key: undefined, value: 20}, {value: 30},
      {key: 'last', value: 40}
    ];
    testDone = false;
    for await (const {key = 'unknown', value} of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternConstDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternTDZConstDeclarationStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    const value = { value: 1 };
    try {
      for await (const {value} of async([value])) sum += value;
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testObjectBindingPatternTDZConstDeclarationStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testObjectBindingPatternTDZConstDeclarationBlockStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    const value = { value: 1 };
    try {
      for await (const {value} of async([value])) {
        sum += value;
      }
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testObjectBindingPatternTDZConstDeclarationBlockStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testArrayBindingPatternConstDeclarationStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (const [key = 'unknown', value] of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternConstDeclarationStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternConstDeclarationBlockStatement() {
    let sum = 0, keys = [];
    let collection = [['first', 10], [undefined, 20], [, 30], ['last', 40]];
    testDone = false;
    for await (const [key = 'unknown', value] of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternLetDeclarationBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 100}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternTDZConstDeclarationStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    const value = [1];
    try {
      for await (const [value] of async([value])) sum += value;
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testArrayBindingPatternTDZConstDeclarationStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  async function testArrayBindingPatternTDZConstDeclarationBlockStatement() {
    // See https://codereview.chromium.org/1218543003
    let sum = 0;
    testDone = false;
    const value = [1];
    try {
      for await (const [value] of async([value])) {
        sum += value;
      }
    } catch (error) {
      threwEarly = true;
      throw { sum, error, toString() { return 'TestError' } };
    }
  }

  threwEarly = false;
  test = testArrayBindingPatternTDZConstDeclarationBlockStatement();
  assertTrue(threwEarly, 'Async function promise should be rejected');
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(0, e.sum);
    assertInstanceof(e.error, ReferenceError);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited promise should be rejected');

  // --------------------------------------------------------------------------

  async function testBindingIdentifierLHSStatement() {
    let sum = 0;
    let value;
    testDone = false;
    for await (value of async([100, 200, 300, 400, 500])) sum += value;
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierLHSStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testBindingIdentifierLHSBlockStatement() {
    let sum = 0;
    let value;
    testDone = false;
    for await (value of async([100, 200, 300, 400, 500])) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      sum += value;
    }
    testDone = true;
    return sum;
  }

  test = testBindingIdentifierLHSStatement();
  assertFalse(testDone);
  assertEquals(1500, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternLHSStatement() {
    let sum = 0;
    let keys = [];
    let value;
    let key;
    let collection = [
      {key: 'first', value: 1}, {key: undefined, value: 2}, {value: 3},
      {key: 'last', value: 4}
    ];
    testDone = false;
    for await ({key = 'unknown', value} of async(collection))
        keys.push(key), sum += value;
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternLHSStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 10}, await test);
  assertTrue(testDone);

  async function testObjectBindingPatternLHSBlockStatement() {
    let sum = 0;
    let keys = [];
    let value;
    let key;
    let collection = [
      {key: 'first', value: 1}, {key: undefined, value: 2}, {value: 3},
      {key: 'last', value: 4}
    ];
    testDone = false;
    for await ({key = 'unknown', value} of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testObjectBindingPatternLHSBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 10}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternLHSStatement() {
    let sum = 0;
    let keys = [];
    let value;
    let key;
    let collection = [['first', 1], [undefined, 2], [, 3], ['last', 4]];
    testDone = false;
    for await ([key = 'unknown', value] of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternLHSStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 10}, await test);
  assertTrue(testDone);

  async function testArrayBindingPatternLHSBlockStatement() {
    let sum = 0;
    let keys = [];
    let value;
    let key;
    let collection = [
      {key: 'first', value: 1}, {key: undefined, value: 2}, {value: 3},
      {key: 'last', value: 4}
    ];
    testDone = false;
    for await ({key = 'unknown', value} of async(collection)) {
      'use strict';
      let strict = (function() { return this === undefined; })();
      assertFalse(strict);
      keys.push(key);
      sum += value;
    }
    testDone = true;
    return {keys, sum};
  }

  test = testArrayBindingPatternLHSBlockStatement();
  assertFalse(testDone);
  assertEquals(
      {keys: ['first', 'unknown', 'unknown', 'last'], sum: 10}, await test);
  assertTrue(testDone);

  // --------------------------------------------------------------------------

  async function testBreakStatementReturnMethodNotPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext, log)) {
      sum += x;
      if (++i === 3) break;
    }
    testDone = true;
    return { sum, log };
  }

  test = testBreakStatementReturnMethodNotPresent();
  assertFalse(testDone);
  assertEquals({sum: 6, log: ['[Symbol.asyncIterator]()',
                              '.next() -> resolved 1',
                              '.next() -> resolved 2',
                              '.next() -> resolved 3']},
               await test);
  assertTrue(testDone);

  async function testBreakStatementReturnMethodPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) break;
    }
    testDone = true;
    return { sum, log };
  }

  test = testBreakStatementReturnMethodPresent();
  assertFalse(testDone);
  assertEquals({sum: 3, log: ['[Symbol.asyncIterator]()',
                              '.next() -> resolved 1',
                              '.next() -> resolved 2',
                              '.return(undefined)']},
               await test);
  assertTrue(testDone);

  async function testBreakStatementReturnMethodAwaitIterResult() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            resolve('break!');
          });
        }),
        done: true
      };
    };

    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) break;
    }
    testDone = true;
    return { sum, log };
  }

  test = testBreakStatementReturnMethodAwaitIterResult();
  assertFalse(testDone);
  assertEquals({sum: 3,
                log: ['[Symbol.asyncIterator]()',
                      '.next() -> resolved 1',
                      '.next() -> resolved 2',
                      '.return() -> resolved break!' ]},
               await test);
  assertTrue(testDone);

  async function testBreakStatementReturnMethodAwaitRejection(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    let sum = 0;
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            reject('break! ' + sum);
          });
        }),
        done: true
      };
    };

    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) break;
    }
    return { sum, log };
  }

  let log = [];
  test = testBreakStatementReturnMethodAwaitRejection(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals(log, ['[Symbol.asyncIterator]()',
                       '.next() -> resolved 1',
                       '.next() -> resolved 2',
                       '.return() -> rejected break! 3']);
    assertEquals('break! 3', e);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');

  async function testBreakStatementReturnMethodPrimitiveValue(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return { value: 'break! primitive!', done: true };
    }
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturnPrimitive, log)) {
      sum += x;
      if (++i === 2) break;
    }
    return { sum, log };
  }
  log = [];
  test = testBreakStatementReturnMethodPrimitiveValue(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> resolved break! primitive!'],
                 log);
    assertInstanceof(e, TypeError);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');

  async function testReturnStatementReturnMethodNotPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext, log)) {
      sum += x;
      if (++i === 3) {
        testDone = true;
        return { sum, log };
      }
    }
  }

  test = testReturnStatementReturnMethodNotPresent();
  assertFalse(testDone);
  assertEquals({sum: 6, log: ['[Symbol.asyncIterator]()',
                              '.next() -> resolved 1',
                              '.next() -> resolved 2',
                              '.next() -> resolved 3']},
               await test);
  assertTrue(testDone);

  async function testReturnStatementReturnMethodPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) {
        testDone = true;
        return { sum, log };
      }
    }
  }

  test = testReturnStatementReturnMethodPresent();
  assertFalse(testDone);
  assertEquals({sum: 3, log: ['[Symbol.asyncIterator]()',
                              '.next() -> resolved 1',
                              '.next() -> resolved 2',
                              '.return(undefined)']},
               await test);
  assertTrue(testDone);

  async function testReturnStatementReturnMethodAwaitIterResult() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            testDone = true;
            resolve('return!');
          });
        }),
        done: true
      };
    };

    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) return { sum, log };
    }
  }

  test = testReturnStatementReturnMethodAwaitIterResult();
  assertFalse(testDone);
  assertEquals({sum: 3,
                log: ['[Symbol.asyncIterator]()',
                      '.next() -> resolved 1',
                      '.next() -> resolved 2',
                      '.return() -> resolved return!' ]},
               await test);
  assertTrue(testDone);

  async function testReturnStatementReturnMethodAwaitRejection(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    let sum = 0;
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            reject('return! ' + sum);
          });
        }),
        done: true
      };
    };

    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) return { sum, log };
    }
  }

  log = [];
  test = testReturnStatementReturnMethodAwaitRejection(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals('return! 3', e);
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> rejected return! 3'],
                 log);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');

  async function testReturnStatementReturnMethodPrimitiveValue(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return { value: 'return! primitive!', done: true };
    }
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturnPrimitive, log)) {
      sum += x;
      if (++i === 2) break;
    }
    return { sum, log };
  }
  log = [];
  test = testReturnStatementReturnMethodPrimitiveValue(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> resolved return! primitive!'],
                 log);
    assertInstanceof(e, TypeError);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');

  async function testThrowStatementReturnMethodNotPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext|kThrow, log)) {
      sum += x;
      if (++i === 3) {
        throw { sum, log, toString() { return 'TestError'; } };
      }
    }
    return { sum, log };
  }

  test = testThrowStatementReturnMethodNotPresent();
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals('TestError', e.toString());
    assertEquals(6, e.sum);
    assertEquals(['[Symbol.asyncIterator]()', '.next() -> resolved 1',
       '.next() -> resolved 2', '.next() -> resolved 3'
      ], e.log);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited Promise should be rejected');

  async function testThrowStatementReturnMethodPresent() {
    let log = [];
    let collection = [1, 2, 3, 4, 5];
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(collection, kNext|kThrow|kReturn, log)) {
      sum += x;
      if (++i === 2) {
        throw { sum, log, toString() { return 'TestError2'; } };
      }
    }
    return { sum, log };
  }

  test = testThrowStatementReturnMethodPresent();
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals('TestError2', e.toString());
    assertEquals(3, e.sum);
    assertEquals(['[Symbol.asyncIterator]()', '.next() -> resolved 1',
       '.next() -> resolved 2', '.return(undefined)'
      ], e.log);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited Promise should be rejected');

  async function testThrowStatementReturnMethodAwaitIterResult(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            testDone = true;
            resolve('throw!');
          });
        }),
        done: true
      };
    };

    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) throw 'Boo!!';
    }
  }

  log = [];
  test = testThrowStatementReturnMethodAwaitIterResult(log);
  assertFalse(testDone);

  try {
    await test;
  } catch (e) {
    assertEquals('Boo!!', e);
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> resolved throw!' ], log);
    testDone = true;
  }
  assertTrue(testDone, 'Awaited Promise should be rejected');

  async function testThrowStatementReturnMethodAwaitRejection(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    let sum = 0;
    sync_iter.return = function() {
      return {
        value: new Promise(function(resolve, reject) {
          Promise.resolve().then(function() {
            reject('return! ' + sum);
          });
        }),
        done: true
      };
    };

    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturn, log)) {
      sum += x;
      if (++i === 2) throw 'Boo!!';
    }
  }

  log = [];
  test = testThrowStatementReturnMethodAwaitRejection(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals('Boo!!', e);
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> rejected return! 3'],
                 log);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');

  async function testThrowStatementReturnMethodPrimitiveValue(log) {
    let collection = [1, 2, 3, 4, 5];
    let sync_iter = collection[Symbol.iterator]();
    sync_iter.return = function() {
      return { value: 'return! primitive!', done: true };
    }
    let sum = 0;
    let i = 0;
    testDone = false;
    for await (var x of async(sync_iter, kNext|kReturnPrimitive, log)) {
      sum += x;
      if (++i === 2) throw 'Boo!!';
    }
  }
  log = [];
  test = testThrowStatementReturnMethodPrimitiveValue(log);
  assertFalse(testDone);
  try {
    await test;
  } catch (e) {
    assertEquals(['[Symbol.asyncIterator]()',
                  '.next() -> resolved 1',
                  '.next() -> resolved 2',
                  '.return() -> resolved return! primitive!'],
                 log);

    // AsyncIteratorClose does not require Throw completions to be of type
    // Object
    assertEquals('Boo!!', e);
    testDone = true;
  }
  assertTrue(testDone, 'Promise should be rejected');
})().catch(function(error) {
  testFailed = true;
  testFailure = error;
});

%RunMicrotasks();

if (testFailed) {
  throw testFailure;
}