Chromium Code Reviews| Index: test/mjsunit/harmony/async-from-sync-iterator.js |
| diff --git a/test/mjsunit/harmony/async-from-sync-iterator.js b/test/mjsunit/harmony/async-from-sync-iterator.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8b1b10375e422538e97d76192f49a9d7694b5113 |
| --- /dev/null |
| +++ b/test/mjsunit/harmony/async-from-sync-iterator.js |
| @@ -0,0 +1,660 @@ |
| +// 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; |
| + |
| +function assertThrowsAsync(run, errorType, message) { |
| + 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); |
| + |
| + %RunMicrotasks(); |
| + |
| + if (!hadError) { |
| + throw new MjsUnitAssertionError( |
| + "Expected " + run + "() to throw " + errorType.name + |
| + ", but did not throw."); |
| + } |
| + if (!(actual instanceof errorType)) |
| + throw new MjsUnitAssertionError( |
| + "Expected " + run + "() to throw " + errorType.name + |
| + ", but threw '" + actual + "'"); |
| + if (message !== void 0 && actual.message !== message) |
| + throw new MjsUnitAssertionError( |
| + "Expected " + run + "() to throw '" + message + "', but threw '" + |
| + actual.message + "'"); |
| +}; |
| + |
| +function resolveLater(value) { |
| + return new Promise(function(resolve) { |
| + Promise.resolve().then(function() { |
| + resolve(value); |
| + }); |
| + }); |
| +} |
| + |
| +function rejectLater(value) { |
| + return new Promise(function(resolve, reject) { |
| + Promise.resolve().then(function() { |
| + reject(value); |
| + }); |
| + }); |
| +} |
| + |
| +const kNext = 1; |
| +const kThrow = 2; |
| +const kReturn = 4; |
| +const kNextThrows = kNext | 8; |
| +const kReturnThrows = kReturn | 16; |
| +const kThrowNormal = kThrow | 32; |
| +const kNextUnchanged = kNext | 64; |
| +const kReturnUnchanged = kReturn | 128; |
| +const kThrowUnchanged = kThrow | 256; |
| +function sync(array, features, log) { |
| + let i = 0; |
| + |
| + // Abort if `log` was not passed, otherwise TypeError may be eaten. |
| + if (log === void 0) %AbortJS("`log` is undefined"); |
|
neis
2017/02/21 12:48:09
Nit: move this up.
caitp
2017/02/21 14:36:53
Acknowledged.
|
| + let methods = { |
| + next(sent) { |
| + let done = i >= array.length; |
| + let value = array[i]; |
| + log.push({ method: "next", sent, value, done }); |
| + if ((features & kNextThrows) === kNextThrows) throw sent; |
| + if ((features & kNextUnchanged) === kNextUnchanged) return sent; |
| + i++; |
| + return { value, done }; |
| + }, |
| + throw(sent) { |
| + let done = i >= array.length; |
| + log.push({ method: "throw", sent, done }); |
| + if ((features & kThrowNormal) === kThrowNormal) |
| + return { value: sent, done }; |
| + if ((features & kThrowUnchanged) === kThrowUnchanged) return sent; |
| + throw sent; |
| + }, |
| + return(sent) { |
| + let done = true; |
| + log.push({ method: "return", sent, done }); |
| + if ((features & kReturnThrows) === kReturnThrows) throw sent; |
| + if ((features & kReturnUnchanged) === kReturnUnchanged) return sent; |
| + return { value: sent, done }; |
| + } |
| + }; |
| + return { |
| + [Symbol.iterator]() { return this; }, |
| + next: (features & kNext) ? methods.next : undefined, |
| + throw: (features & kThrow) ? methods.throw : undefined, |
| + return: (features & kReturn) ? methods.return : undefined |
| + }; |
| +} |
| + |
| +class MyError extends Error {}; |
| + |
| +(async function AsyncFromSyncWithGenerator() { |
| + function* gen() { |
| + yield "sync value"; |
| + try { |
| + yield new Promise(function(resolve) { |
| + resolve("async value"); |
| + }); |
| + } catch (error) { |
| + throw error; |
| + } |
| + assertUnreachable("generator is closed"); |
| + } |
| + let iter = %CreateAsyncFromSyncIterator(gen()); |
| + |
| + // [Async-from-Sync Iterator] wraps sync iterator values in a Promise |
| + let promise = iter.next(); |
| + assertInstanceof(promise, Promise); |
| + let iter_result = await promise; |
| + assertEquals({ value: "sync value", done: false }, iter_result); |
| + |
| + // [Async-from-Sync Iterator] will wait for resolution of Promise values |
| + promise = iter.next(); |
| + assertInstanceof(promise, Promise); |
| + iter_result = await promise; |
| + assertEquals({ value: "async value", done: false }, iter_result); |
| + |
| + // [Async-from-Sync Iterator].throw delegates to .throw() method of sync |
| + // iterator. |
| + promise = iter.throw(new MyError("Error#1")); |
| + assertInstanceof(promise, Promise); |
| + try { |
| + await promise; |
| + assertUnreachable("promise should be rejected"); |
| + } catch (e) { |
| + assertInstanceof(e, MyError); |
| + assertEquals("Error#1", e.message); |
| + } |
| + |
| + promise = iter.return("generator closed"); |
| + assertInstanceof(promise, Promise); |
| + iter_result = await promise; |
| + assertEquals({ value: "generator closed", done: true }, iter_result); |
| + |
| + // .next(), .return() and .throw() delegate to sync iterator methods, without |
| + // keeping track of the state of the generator. |
| + promise = iter.next("unused"); |
| + assertInstanceof(promise, Promise); |
| + iter_result = await promise; |
| + assertEquals({ value: undefined, done: true }, iter_result); |
| + |
| + promise = iter.throw(new MyError("Error#2")); |
| + assertInstanceof(promise, Promise); |
| + try { |
| + await promise; |
| + } catch (e) { |
| + assertInstanceof(e, MyError); |
| + assertEquals("Error#2", e.message); |
| + } |
| + |
| + promise = iter.return("return-after-completed"); |
| + assertInstanceof(promise, Promise); |
| + iter_result = await promise; |
| + assertEquals({ value: "return-after-completed", done: true }, iter_result); |
| +})().catch(function(error) { |
| + testFailed = true; |
| + testFailure = error; |
| +}); |
| + |
| +%RunMicrotasks(); |
| +if (testFailed) { |
| + throw testFailure; |
| +} |
| + |
| + |
| +(async function AsyncFromSyncOrderOfOperations() { |
| + let log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], 0, log)); |
| + |
| + try { |
| + await iter.next(); |
| + assertUnreachable("Iterator.next() method is not optional"); |
| + } catch (e) { |
| + assertInstanceof(e, TypeError); |
| + assertEquals([], log); |
| + } |
| + |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], kNext, log)); |
| + assertEquals({ value: "sync-value", done: false }, await iter.next("a")); |
| + assertEquals([ |
| + { |
| + method: "next", |
| + sent: "a", |
| + value: "sync-value", |
| + done: false |
| + } |
| + ], log); |
| + |
| + log = []; |
| + let asyncValue = resolveLater("async-value"); |
| + iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
| + assertEquals({ value: "async-value", done: false }, await iter.next("b")); |
| + assertEquals([ |
| + { |
| + method: "next", |
| + sent: "b", |
| + value: asyncValue, |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator].next() produces a rejected Promise or an exception is |
| + // thrown, Promise is rejected with thrown/rejected value. |
| + log = []; |
| + asyncValue = rejectLater("Boo!"); |
| + iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
| + try { |
| + await iter.next('c'); |
| + assertUnreachable('Expected `iter.next(\'c\') to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals("Boo!", e); |
| + assertEquals([ |
| + { |
| + method: 'next', |
| + sent: 'c', |
| + value: asyncValue, |
| + done: false |
| + } |
| + ], log); |
| + } |
| + |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNextThrows, log)); |
| + try { |
| + await iter.next('Boo!'); |
| + assertUnreachable('Expected `iter.next(\'c\') to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals("Boo!", e); |
| + assertEquals([ |
| + { |
| + method: 'next', |
| + sent: 'Boo!', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + } |
| + |
| + |
| + // [Async-from-Sync Iterator].next() will be rejected with a TypeError if |
| + // Type([sync_iterator].next()) is not Object. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], |
| + kNext|kNextUnchanged, log)); |
|
neis
2017/02/21 12:48:09
Nit: kNextUnchanged includes kNext.
caitp
2017/02/21 14:36:52
Acknowledged.
|
| + try { |
| + await iter.next('not-a-JSReceiver'); |
| + assertUnreachable('Expected `iter.next(\'not-a-JSReceiver\')` to ' + |
| + 'throw, but did not throw') |
| + } catch (e) { |
| + assertEquals(e.constructor, TypeError); |
| + } |
| + |
| + assertEquals([ |
| + { |
| + method: 'next', |
| + sent: 'not-a-JSReceiver', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator] does not have a .return() method, return a Promise |
| + // resolved with the value `{ value: <<sent value>>, done: true }`. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], kNext, log)); |
| + assertEquals({ |
| + value: 'd', |
| + done: true |
| + }, await iter.return('d')); |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ |
| + value: 'sync-return', |
| + done: false |
| + }, await iter.next('e')); |
| + |
| + assertEquals([ |
| + { |
| + method: 'next', |
| + sent: 'e', |
| + value: 'sync-return', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator] does have a .return() method, return a Promise |
| + // fulfilled with the iterator result of [sync_iterator].return(). |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], |
| + kNext|kReturn, log)); |
| + assertEquals({ |
| + value: 'f', |
| + done: true |
| + }, await iter.return('f')); |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ |
| + value: 'sync-return', |
| + done: false |
| + }, await iter.next('g')); |
| + |
| + assertEquals([ |
| + { |
| + method: 'return', |
| + sent: 'f', |
| + done: true |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'g', |
| + value: 'sync-return', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator].return() produces a rejected Promise or an exception is |
| + // thrown, Promise is rejected with thrown/rejected value. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kReturnThrows, |
| + log)); |
| + try { |
| + await iter.return('Boo!!'); |
| + assertUnreachable('Expected `iter.return(\'Boo!!\')` to throw, but did ' + |
| + 'not throw'); |
| + } catch (e) { |
| + assertEquals("Boo!!", e); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('h')); |
| + assertEquals([ |
| + { |
| + method: 'return', |
| + sent: 'Boo!!', |
| + done: true |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'h', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kReturn, log)); |
| + |
| + let rejection = Promise.reject('Boo!!'); |
| + try { |
| + await iter.return(rejection); |
| + assertUnreachable('Expected `iter.return(Promise.reject(\'Boo!!\'))` to ' + |
| + 'throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('Boo!!', e); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('i')); |
| + assertEquals([ |
| + { |
| + method: 'return', |
| + sent: rejection, |
| + done: true |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'i', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // [Async-from-Sync Iterator].return() will be rejected with a TypeError if |
| + // Type([sync_iterator].return()) is not Object. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], |
| + kNext|kReturnUnchanged, log)); |
| + try { |
| + await iter.return('not-a-JSReceiver'); |
| + assertUnreachable('Expected `iter.return(\'not-a-JSReceiver\')` to ' + |
| + 'throw, but did not throw') |
| + } catch (e) { |
| + assertEquals(e.constructor, TypeError); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('j')); |
| + assertEquals([ |
| + { |
| + method: 'return', |
| + sent: 'not-a-JSReceiver', |
| + done: true |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'j', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator] does not have a .throw method, return a Promise rejected |
| + // with the sent value. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext, log)); |
| + try { |
| + await iter.throw('Boo!!'); |
| + assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
| + 'throw'); |
| + } catch (e) { |
| + assertEquals('Boo!!', e); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('k')); |
| + assertEquals([ |
| + { |
| + method: 'next', |
| + sent: 'k', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrow, log)); |
| + try { |
| + await iter.throw('Boo!!'); |
| + assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
| + 'throw'); |
| + } catch (e) { |
| + assertEquals('Boo!!', e); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('l')); |
| + assertEquals([ |
| + { |
| + method: 'throw', |
| + sent: 'Boo!!', |
| + done: false |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'l', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // If [sync_iterator].throw() returns a resolved Promise or a Completion |
| + // with [[Type]] "normal" or "return", return a resolved Promise |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrowNormal, |
| + log)); |
| + assertEquals({ |
| + value: 'Boo!!', |
| + done: false |
| + }, await iter.throw('Boo!!')); |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('m')); |
| + assertEquals([ |
| + { |
| + method: 'throw', |
| + sent: 'Boo!!', |
| + done: false |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'm', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // [Async-from-Sync Iterator].throw() will be rejected with a TypeError if |
| + // Type([sync_iterator].throw()) is not Object. |
| + log = []; |
| + iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], |
| + kNext|kThrowUnchanged, log)); |
| + try { |
| + await iter.throw('not-a-JSReceiver'); |
| + assertUnreachable('Expected `iter.throw(\'not-a-JSReceiver\')` to ' + |
| + 'throw, but did not throw') |
| + } catch (e) { |
| + assertEquals(e.constructor, TypeError); |
| + } |
| + |
| + // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
| + // whether [sync_iterator] is completed or not. |
| + assertEquals({ value: 'sync-value', done: false }, await iter.next('n')); |
| + assertEquals([ |
| + { |
| + method: 'throw', |
| + sent: 'not-a-JSReceiver', |
| + done: false |
| + }, |
| + { |
| + method: 'next', |
| + sent: 'n', |
| + value: 'sync-value', |
| + done: false |
| + } |
| + ], log); |
| + |
| + // Let nextValue be IteratorValue(nextResult). |
| + // IfAbruptRejectPromise(nextValue, promiseCapability).) |
| + iter = %CreateAsyncFromSyncIterator({ |
| + next() { return { get value() { throw "BadValue!" }, done: false }; } |
| + }); |
| + try { |
| + await iter.next(); |
| + assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| + |
| + // Let nextDone be IteratorComplete(nextResult). |
| + // IfAbruptRejectPromise(nextDone, promiseCapability). |
| + iter = %CreateAsyncFromSyncIterator({ |
| + next() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| + }); |
| + try { |
| + await iter.next(); |
| + assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| + |
| + // IfAbruptRejectPromise(returnResult, promiseCapability). |
| + // Let returnValue be IteratorValue(returnResult). |
| + iter = %CreateAsyncFromSyncIterator({ |
| + return() { return { get value() { throw "BadValue!" }, done: false }; } |
| + }); |
| + try { |
| + await iter.return(); |
| + assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| + |
| + // IfAbruptRejectPromise(returnValue, promiseCapability). |
| + // Let returnDone be IteratorComplete(returnResult). |
| + iter = %CreateAsyncFromSyncIterator({ |
| + return() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| + }); |
| + try { |
| + await iter.return(); |
| + assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| + |
| + // IfAbruptRejectPromise(throwResult, promiseCapability). |
| + // Let throwValue be IteratorValue(throwResult). |
| + iter = %CreateAsyncFromSyncIterator({ |
| + throw() { return { get value() { throw "BadValue!" }, done: false }; } |
| + }); |
| + try { |
| + await iter.throw(); |
| + assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| + |
| + // IfAbruptRejectPromise(throwValue, promiseCapability). |
| + // Let throwDone be IteratorComplete(throwResult). |
| + iter = %CreateAsyncFromSyncIterator({ |
| + throw() { return { value: undefined, get done() { throw "BadValue!" } }; } |
| + }); |
| + try { |
| + await iter.throw(); |
| + assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
| + } catch (e) { |
| + assertEquals('BadValue!', e); |
| + } |
| +})().catch(function(error) { |
| + testFailed = true; |
| + testFailure = error; |
| +}); |
| + |
| +%RunMicrotasks(); |
| +if (testFailed) { |
| + throw testFailure; |
| +} |
| + |
| +(function ExtractedAsyncFromSyncIteratorMethods() { |
| + // Async-from-Sync iterator methods can be extracted via function.caller. |
| + // TODO(caitp): test extracted `throw` method using yield* in async generator. |
| + let extractor = [0, 1, 2, 3, 4,5,6,7,8,9]; |
| + let extractedNext; |
| + let extractedReturn; |
| + |
| + extractor[Symbol.iterator] = function() { |
| + let it = [][Symbol.iterator].call(extractor); |
| + let origNext = it.next, origThrow = it.throw, origReturn = it.return; |
| + function extractNext() { |
| + extractedNext = extractNext.caller; |
| + return origNext; |
| + } |
| + function extractReturn() { |
| + extractedReturn = extractReturn.caller; |
| + return origReturn; |
| + } |
| + Object.defineProperties(it, { |
| + "next": { get: extractNext, configurable: true }, |
| + "return": { get: extractReturn, configurable: true } |
| + }); |
| + return it; |
| + }; |
| + |
| + async function f() { |
| + let i; |
| + let it = extractor[Symbol.iterator](); |
| + for await (let x of it) break; |
| + for await (let x of it) return "x"; |
| + } |
| + |
| + // Cycle through `f` to extract iterator methods |
| + f().catch(function() { assertUnreachable("No error should have occurred"); }); |
|
neis
2017/02/21 12:48:09
This assertUnreachable would go unnoticed.
caitp
2017/02/21 14:36:52
Good point, using %AbortJS instead.
|
| + %RunMicrotasks(); |
| + |
| + assertEquals(typeof extractedNext, "function"); |
| + assertThrowsAsync(() => extractedNext.call(undefined), TypeError); |
| + assertThrowsAsync(() => extractedNext.call(1), TypeError); |
| + |
| + assertEquals(typeof extractedReturn, "function"); |
| + assertThrowsAsync(() => extractedReturn.call(undefined), TypeError); |
| + assertThrowsAsync(() => extractedReturn.call(1), TypeError); |
| +})(); |