summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/streams/readable-streams/async-iterator.any.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/streams/readable-streams/async-iterator.any.js')
-rw-r--r--testing/web-platform/tests/streams/readable-streams/async-iterator.any.js650
1 files changed, 650 insertions, 0 deletions
diff --git a/testing/web-platform/tests/streams/readable-streams/async-iterator.any.js b/testing/web-platform/tests/streams/readable-streams/async-iterator.any.js
new file mode 100644
index 0000000000..3ccaca17bc
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/async-iterator.any.js
@@ -0,0 +1,650 @@
+// META: global=window,worker
+// META: script=../resources/rs-utils.js
+// META: script=../resources/test-utils.js
+// META: script=../resources/recording-streams.js
+'use strict';
+
+const error1 = new Error('error1');
+
+function assert_iter_result(iterResult, value, done, message) {
+ const prefix = message === undefined ? '' : `${message} `;
+ assert_equals(typeof iterResult, 'object', `${prefix}type is object`);
+ assert_equals(Object.getPrototypeOf(iterResult), Object.prototype, `${prefix}[[Prototype]]`);
+ assert_array_equals(Object.getOwnPropertyNames(iterResult).sort(), ['done', 'value'], `${prefix}property names`);
+ assert_equals(iterResult.value, value, `${prefix}value`);
+ assert_equals(iterResult.done, done, `${prefix}done`);
+}
+
+test(() => {
+ const s = new ReadableStream();
+ const it = s.values();
+ const proto = Object.getPrototypeOf(it);
+
+ const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype);
+ assert_equals(Object.getPrototypeOf(proto), AsyncIteratorPrototype, 'prototype should extend AsyncIteratorPrototype');
+
+ const methods = ['next', 'return'].sort();
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), methods, 'should have all the correct methods');
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_true(propDesc.enumerable, 'method should be enumerable');
+ assert_true(propDesc.configurable, 'method should be configurable');
+ assert_true(propDesc.writable, 'method should be writable');
+ assert_equals(typeof it[m], 'function', 'method should be a function');
+ assert_equals(it[m].name, m, 'method should have the correct name');
+ }
+
+ assert_equals(it.next.length, 0, 'next should have no parameters');
+ assert_equals(it.return.length, 1, 'return should have 1 parameter');
+ assert_equals(typeof it.throw, 'undefined', 'throw should not exist');
+}, 'Async iterator instances should have the correct list of properties');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(1);
+ c.enqueue(2);
+ c.enqueue(3);
+ c.close();
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [1, 2, 3]);
+}, 'Async-iterating a push source');
+
+promise_test(async () => {
+ let i = 1;
+ const s = new ReadableStream({
+ pull(c) {
+ c.enqueue(i);
+ if (i >= 3) {
+ c.close();
+ }
+ i += 1;
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [1, 2, 3]);
+}, 'Async-iterating a pull source');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(undefined);
+ c.enqueue(undefined);
+ c.enqueue(undefined);
+ c.close();
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [undefined, undefined, undefined]);
+}, 'Async-iterating a push source with undefined values');
+
+promise_test(async () => {
+ let i = 1;
+ const s = new ReadableStream({
+ pull(c) {
+ c.enqueue(undefined);
+ if (i >= 3) {
+ c.close();
+ }
+ i += 1;
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [undefined, undefined, undefined]);
+}, 'Async-iterating a pull source with undefined values');
+
+promise_test(async () => {
+ let i = 1;
+ const s = recordingReadableStream({
+ pull(c) {
+ c.enqueue(i);
+ if (i >= 3) {
+ c.close();
+ }
+ i += 1;
+ },
+ }, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const it = s.values();
+ assert_array_equals(s.events, []);
+
+ const read1 = await it.next();
+ assert_iter_result(read1, 1, false);
+ assert_array_equals(s.events, ['pull']);
+
+ const read2 = await it.next();
+ assert_iter_result(read2, 2, false);
+ assert_array_equals(s.events, ['pull', 'pull']);
+
+ const read3 = await it.next();
+ assert_iter_result(read3, 3, false);
+ assert_array_equals(s.events, ['pull', 'pull', 'pull']);
+
+ const read4 = await it.next();
+ assert_iter_result(read4, undefined, true);
+ assert_array_equals(s.events, ['pull', 'pull', 'pull']);
+}, 'Async-iterating a pull source manually');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.error('e');
+ },
+ });
+
+ try {
+ for await (const chunk of s) {}
+ assert_unreached();
+ } catch (e) {
+ assert_equals(e, 'e');
+ }
+}, 'Async-iterating an errored stream throws');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+
+ for await (const chunk of s) {
+ assert_unreached();
+ }
+}, 'Async-iterating a closed stream never executes the loop body, but works fine');
+
+promise_test(async () => {
+ const s = new ReadableStream();
+
+ const loop = async () => {
+ for await (const chunk of s) {
+ assert_unreached();
+ }
+ assert_unreached();
+ };
+
+ await Promise.race([
+ loop(),
+ flushAsyncEvents()
+ ]);
+}, 'Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(1);
+ c.enqueue(2);
+ c.enqueue(3);
+ c.close();
+ },
+ });
+
+ const reader = s.getReader();
+ const readResult = await reader.read();
+ assert_iter_result(readResult, 1, false);
+ reader.releaseLock();
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [2, 3]);
+}, 'Async-iterating a partially consumed stream');
+
+for (const type of ['throw', 'break', 'return']) {
+ for (const preventCancel of [false, true]) {
+ promise_test(async () => {
+ const s = recordingReadableStream({
+ start(c) {
+ c.enqueue(0);
+ }
+ });
+
+ // use a separate function for the loop body so return does not stop the test
+ const loop = async () => {
+ for await (const c of s.values({ preventCancel })) {
+ if (type === 'throw') {
+ throw new Error();
+ } else if (type === 'break') {
+ break;
+ } else if (type === 'return') {
+ return;
+ }
+ }
+ };
+
+ try {
+ await loop();
+ } catch (e) {}
+
+ if (preventCancel) {
+ assert_array_equals(s.events, ['pull'], `cancel() should not be called`);
+ } else {
+ assert_array_equals(s.events, ['pull', 'cancel', undefined], `cancel() should be called`);
+ }
+ }, `Cancellation behavior when ${type}ing inside loop body; preventCancel = ${preventCancel}`);
+ }
+}
+
+for (const preventCancel of [false, true]) {
+ promise_test(async () => {
+ const s = recordingReadableStream({
+ start(c) {
+ c.enqueue(0);
+ }
+ });
+
+ const it = s.values({ preventCancel });
+ await it.return();
+
+ if (preventCancel) {
+ assert_array_equals(s.events, [], `cancel() should not be called`);
+ } else {
+ assert_array_equals(s.events, ['cancel', undefined], `cancel() should be called`);
+ }
+ }, `Cancellation behavior when manually calling return(); preventCancel = ${preventCancel}`);
+}
+
+promise_test(async t => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResult1 = await it.next();
+ assert_iter_result(iterResult1, 0, false, '1st next()');
+
+ await promise_rejects_exactly(t, error1, it.next(), '2nd next()');
+}, 'next() rejects if the stream errors');
+
+promise_test(async () => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResult = await it.return('return value');
+ assert_iter_result(iterResult, 'return value', true);
+}, 'return() does not rejects if the stream has not errored yet');
+
+promise_test(async t => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ // Do not error in start() because doing so would prevent acquiring a reader/async iterator.
+ c.error(error1);
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ await flushAsyncEvents();
+ await promise_rejects_exactly(t, error1, it.return('return value'));
+}, 'return() rejects if the stream has errored');
+
+promise_test(async t => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResult1 = await it.next();
+ assert_iter_result(iterResult1, 0, false, '1st next()');
+
+ await promise_rejects_exactly(t, error1, it.next(), '2nd next()');
+
+ const iterResult3 = await it.next();
+ assert_iter_result(iterResult3, undefined, true, '3rd next()');
+}, 'next() that succeeds; next() that reports an error; next()');
+
+promise_test(async () => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResults = await Promise.allSettled([it.next(), it.next(), it.next()]);
+
+ assert_equals(iterResults[0].status, 'fulfilled', '1st next() promise status');
+ assert_iter_result(iterResults[0].value, 0, false, '1st next()');
+
+ assert_equals(iterResults[1].status, 'rejected', '2nd next() promise status');
+ assert_equals(iterResults[1].reason, error1, '2nd next() rejection reason');
+
+ assert_equals(iterResults[2].status, 'fulfilled', '3rd next() promise status');
+ assert_iter_result(iterResults[2].value, undefined, true, '3rd next()');
+}, 'next() that succeeds; next() that reports an error(); next() [no awaiting]');
+
+promise_test(async t => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResult1 = await it.next();
+ assert_iter_result(iterResult1, 0, false, '1st next()');
+
+ await promise_rejects_exactly(t, error1, it.next(), '2nd next()');
+
+ const iterResult3 = await it.return('return value');
+ assert_iter_result(iterResult3, 'return value', true, 'return()');
+}, 'next() that succeeds; next() that reports an error(); return()');
+
+promise_test(async () => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResults = await Promise.allSettled([it.next(), it.next(), it.return('return value')]);
+
+ assert_equals(iterResults[0].status, 'fulfilled', '1st next() promise status');
+ assert_iter_result(iterResults[0].value, 0, false, '1st next()');
+
+ assert_equals(iterResults[1].status, 'rejected', '2nd next() promise status');
+ assert_equals(iterResults[1].reason, error1, '2nd next() rejection reason');
+
+ assert_equals(iterResults[2].status, 'fulfilled', 'return() promise status');
+ assert_iter_result(iterResults[2].value, 'return value', true, 'return()');
+}, 'next() that succeeds; next() that reports an error(); return() [no awaiting]');
+
+promise_test(async () => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ c.enqueue(timesPulled);
+ ++timesPulled;
+ }
+ });
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResult1 = await it.next();
+ assert_iter_result(iterResult1, 0, false, 'next()');
+
+ const iterResult2 = await it.return('return value');
+ assert_iter_result(iterResult2, 'return value', true, 'return()');
+
+ assert_equals(timesPulled, 2);
+}, 'next() that succeeds; return()');
+
+promise_test(async () => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ c.enqueue(timesPulled);
+ ++timesPulled;
+ }
+ });
+ const it = s[Symbol.asyncIterator]();
+
+ const iterResults = await Promise.allSettled([it.next(), it.return('return value')]);
+
+ assert_equals(iterResults[0].status, 'fulfilled', 'next() promise status');
+ assert_iter_result(iterResults[0].value, 0, false, 'next()');
+
+ assert_equals(iterResults[1].status, 'fulfilled', 'return() promise status');
+ assert_iter_result(iterResults[1].value, 'return value', true, 'return()');
+
+ assert_equals(timesPulled, 2);
+}, 'next() that succeeds; return() [no awaiting]');
+
+promise_test(async () => {
+ const rs = new ReadableStream();
+ const it = rs.values();
+
+ const iterResult1 = await it.return('return value');
+ assert_iter_result(iterResult1, 'return value', true, 'return()');
+
+ const iterResult2 = await it.next();
+ assert_iter_result(iterResult2, undefined, true, 'next()');
+}, 'return(); next()');
+
+promise_test(async () => {
+ const rs = new ReadableStream();
+ const it = rs.values();
+
+ const iterResults = await Promise.allSettled([it.return('return value'), it.next()]);
+
+ assert_equals(iterResults[0].status, 'fulfilled', 'return() promise status');
+ assert_iter_result(iterResults[0].value, 'return value', true, 'return()');
+
+ assert_equals(iterResults[1].status, 'fulfilled', 'next() promise status');
+ assert_iter_result(iterResults[1].value, undefined, true, 'next()');
+}, 'return(); next() [no awaiting]');
+
+promise_test(async () => {
+ const rs = new ReadableStream();
+ const it = rs.values();
+
+ const iterResult1 = await it.return('return value 1');
+ assert_iter_result(iterResult1, 'return value 1', true, '1st return()');
+
+ const iterResult2 = await it.return('return value 2');
+ assert_iter_result(iterResult2, 'return value 2', true, '1st return()');
+}, 'return(); return()');
+
+promise_test(async () => {
+ const rs = new ReadableStream();
+ const it = rs.values();
+
+ const iterResults = await Promise.allSettled([it.return('return value 1'), it.return('return value 2')]);
+
+ assert_equals(iterResults[0].status, 'fulfilled', '1st return() promise status');
+ assert_iter_result(iterResults[0].value, 'return value 1', true, '1st return()');
+
+ assert_equals(iterResults[1].status, 'fulfilled', '2nd return() promise status');
+ assert_iter_result(iterResults[1].value, 'return value 2', true, '1st return()');
+}, 'return(); return() [no awaiting]');
+
+test(() => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(0);
+ c.close();
+ },
+ });
+ s.values();
+ assert_throws_js(TypeError, () => s.values(), 'values() should throw');
+}, 'values() throws if there\'s already a lock');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(1);
+ c.enqueue(2);
+ c.enqueue(3);
+ c.close();
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, [1, 2, 3]);
+
+ const reader = s.getReader();
+ await reader.closed;
+}, 'Acquiring a reader after exhaustively async-iterating a stream');
+
+promise_test(async t => {
+ let timesPulled = 0;
+ const s = new ReadableStream({
+ pull(c) {
+ if (timesPulled === 0) {
+ c.enqueue(0);
+ ++timesPulled;
+ } else {
+ c.error(error1);
+ }
+ }
+ });
+
+ const it = s[Symbol.asyncIterator]({ preventCancel: true });
+
+ const iterResult1 = await it.next();
+ assert_iter_result(iterResult1, 0, false, '1st next()');
+
+ await promise_rejects_exactly(t, error1, it.next(), '2nd next()');
+
+ const iterResult2 = await it.return('return value');
+ assert_iter_result(iterResult2, 'return value', true, 'return()');
+
+ // i.e. it should not reject with a generic "this stream is locked" TypeError.
+ const reader = s.getReader();
+ await promise_rejects_exactly(t, error1, reader.closed, 'closed on the new reader should reject with the error');
+}, 'Acquiring a reader after return()ing from a stream that errors');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(1);
+ c.enqueue(2);
+ c.enqueue(3);
+ c.close();
+ },
+ });
+
+ // read the first two chunks, then cancel
+ const chunks = [];
+ for await (const chunk of s) {
+ chunks.push(chunk);
+ if (chunk >= 2) {
+ break;
+ }
+ }
+ assert_array_equals(chunks, [1, 2]);
+
+ const reader = s.getReader();
+ await reader.closed;
+}, 'Acquiring a reader after partially async-iterating a stream');
+
+promise_test(async () => {
+ const s = new ReadableStream({
+ start(c) {
+ c.enqueue(1);
+ c.enqueue(2);
+ c.enqueue(3);
+ c.close();
+ },
+ });
+
+ // read the first two chunks, then release lock
+ const chunks = [];
+ for await (const chunk of s.values({preventCancel: true})) {
+ chunks.push(chunk);
+ if (chunk >= 2) {
+ break;
+ }
+ }
+ assert_array_equals(chunks, [1, 2]);
+
+ const reader = s.getReader();
+ const readResult = await reader.read();
+ assert_iter_result(readResult, 3, false);
+ await reader.closed;
+}, 'Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true');
+
+for (const preventCancel of [false, true]) {
+ test(() => {
+ const rs = new ReadableStream();
+ rs.values({ preventCancel }).return();
+ // The test passes if this line doesn't throw.
+ rs.getReader();
+ }, `return() should unlock the stream synchronously when preventCancel = ${preventCancel}`);
+}
+
+promise_test(async () => {
+ const rs = new ReadableStream({
+ async start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ await flushAsyncEvents();
+ // At this point, the async iterator has a read request in the stream's queue for its pending next() promise.
+ // Closing the stream now causes two things to happen *synchronously*:
+ // 1. ReadableStreamClose resolves reader.[[closedPromise]] with undefined.
+ // 2. ReadableStreamClose calls the read request's close steps, which calls ReadableStreamReaderGenericRelease,
+ // which replaces reader.[[closedPromise]] with a rejected promise.
+ c.close();
+ }
+ });
+
+ const chunks = [];
+ for await (const chunk of rs) {
+ chunks.push(chunk);
+ }
+ assert_array_equals(chunks, ['a', 'b', 'c']);
+}, 'close() while next() is pending');