author | Mattias Buelens <mattias@buelens.com> |
Mon, 18 Feb 2019 19:25:06 +0000 | |
changeset 461109 | daffa4526e1850355356d0131230159b9eed8724 |
parent 461108 | 0390eb5bdf53561523d61429d7ffbf268ad1429a |
child 461110 | ebae3e84214df938e7d5091ea9f6efd8adfec3d4 |
push id | 112159 |
push user | james@hoppipolla.co.uk |
push date | Tue, 26 Feb 2019 12:08:48 +0000 |
treeherder | mozilla-inbound@20be3ebad986 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | testonly |
bugs | 1526596, 15097 |
milestone | 67.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/streams/readable-streams/async-iterator.any.js @@ -0,0 +1,340 @@ +// META: global=worker,jsshell +// META: script=../resources/rs-utils.js +// META: script=../resources/test-utils.js +// META: script=../resources/recording-streams.js +'use strict'; + +test(() => { + assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.getIterator); +}, '@@asyncIterator() method is === to getIterator() method'); + +test(() => { + const s = new ReadableStream(); + const it = s.getIterator(); + 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_false(propDesc.enumerable, 'method should be non-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 () => { + 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.getIterator(); + assert_array_equals(s.events, []); + + const read1 = await it.next(); + assert_equals(read1.done, false); + assert_equals(read1.value, 1); + assert_array_equals(s.events, ['pull']); + + const read2 = await it.next(); + assert_equals(read2.done, false); + assert_equals(read2.value, 2); + assert_array_equals(s.events, ['pull', 'pull']); + + const read3 = await it.next(); + assert_equals(read3.done, false); + assert_equals(read3.value, 3); + assert_array_equals(s.events, ['pull', 'pull', 'pull']); + + const read4 = await it.next(); + assert_equals(read4.done, true); + assert_equals(read4.value, undefined); + 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_equals(readResult.done, false); + assert_equals(readResult.value, 1); + 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.getIterator({ 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.getIterator({ 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 () => { + const s = new ReadableStream(); + const it = s[Symbol.asyncIterator](); + await it.return(); + try { + await it.return(); + assert_unreached(); + } catch (e) {} +}, 'Calling return() twice rejects'); + +promise_test(async () => { + const s = new ReadableStream({ + start(c) { + c.enqueue(0); + c.close(); + }, + }); + const it = s[Symbol.asyncIterator](); + const next = await it.next(); + assert_equals(Object.getPrototypeOf(next), Object.prototype); + assert_array_equals(Object.getOwnPropertyNames(next).sort(), ['done', 'value']); +}, 'next()\'s fulfillment value has the right shape'); + +promise_test(async t => { + const s = recordingReadableStream(); + const it = s[Symbol.asyncIterator](); + it.next(); + + await promise_rejects(t, new TypeError(), it.return(), 'return() should reject'); + assert_array_equals(s.events, ['pull']); +}, 'calling return() while there are pending reads rejects'); + +test(() => { + const s = new ReadableStream({ + start(c) { + c.enqueue(0); + c.close(); + }, + }); + const it = s.getIterator(); + assert_throws(new TypeError(), () => s.getIterator(), 'getIterator() should throw'); +}, 'getIterator() 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 () => { + 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.getIterator({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_equals(readResult.done, false, 'should not be closed yet'); + assert_equals(readResult.value, 3, 'should read remaining chunk'); + await reader.closed; +}, 'Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true');
--- a/testing/web-platform/tests/streams/readable-streams/brand-checks.any.js +++ b/testing/web-platform/tests/streams/readable-streams/brand-checks.any.js @@ -1,14 +1,15 @@ // META: global=worker,jsshell // META: script=../resources/test-utils.js 'use strict'; let ReadableStreamDefaultReader; let ReadableStreamDefaultController; +let ReadableStreamAsyncIteratorPrototype; test(() => { // It's not exposed globally, but we test a few of its properties here. ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor; }, 'Can get the ReadableStreamDefaultReader constructor indirectly'); @@ -18,16 +19,23 @@ test(() => { new ReadableStream({ start(c) { ReadableStreamDefaultController = c.constructor; } }); }, 'Can get the ReadableStreamDefaultController constructor indirectly'); +test(() => { + + const rs = new ReadableStream(); + ReadableStreamAsyncIteratorPrototype = Object.getPrototypeOf(rs.getIterator()); + +}, 'Can get ReadableStreamAsyncIteratorPrototype object indirectly'); + function fakeRS() { return Object.setPrototypeOf({ cancel() { return Promise.resolve(); }, getReader() { return new ReadableStreamDefaultReader(new ReadableStream()); }, pipeThrough(obj) { return obj.readable; }, pipeTo() { return Promise.resolve(); }, tee() { return [realRS(), realRS()]; } }, ReadableStream.prototype); @@ -63,16 +71,23 @@ function realRSDefaultController() { new ReadableStream({ start(c) { controller = c; } }); return controller; } +function fakeRSAsyncIterator() { + return Object.setPrototypeOf({ + next() { }, + return(value = undefined) { } + }, ReadableStreamAsyncIteratorPrototype); +} + promise_test(t => { return methodRejectsForAll(t, ReadableStream.prototype, 'cancel', [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]); }, 'ReadableStream.prototype.cancel enforces a brand check'); test(() => { @@ -152,8 +167,22 @@ test(() => { }, 'ReadableStreamDefaultController.prototype.enqueue enforces a brand check'); test(() => { methodThrowsForAll(ReadableStreamDefaultController.prototype, 'error', [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]); }, 'ReadableStreamDefaultController.prototype.error enforces a brand check'); + +promise_test(t => { + + return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'next', + [fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]); + +}, 'ReadableStreamAsyncIteratorPrototype.next enforces a brand check'); + +promise_test(t => { + + return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'return', + [fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]); + +}, 'ReadableStreamAsyncIteratorPrototype.return enforces a brand check');
--- a/testing/web-platform/tests/streams/readable-streams/general.any.js +++ b/testing/web-platform/tests/streams/readable-streams/general.any.js @@ -34,23 +34,25 @@ test(() => { 'constructor should throw when the type is asdf'); assert_throws(error1, () => new ReadableStream({ type: { get toString() {throw error1;} } }), 'constructor should throw when ToString() throws'); assert_throws(error1, () => new ReadableStream({ type: { toString() {throw error1;} } }), 'constructor should throw when ToString() throws'); }, 'ReadableStream can\'t be constructed with an invalid type'); test(() => { - const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee']; + const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee', 'getIterator']; const properties = methods.concat(['locked']).sort(); + const symbols = [Symbol.asyncIterator]; const rs = new ReadableStream(); const proto = Object.getPrototypeOf(rs); - assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct methods'); + assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct properties'); + assert_array_equals(Object.getOwnPropertySymbols(proto).sort(), symbols, 'should have all the correct symbols'); for (const m of methods) { const propDesc = Object.getOwnPropertyDescriptor(proto, m); assert_false(propDesc.enumerable, 'method should be non-enumerable'); assert_true(propDesc.configurable, 'method should be configurable'); assert_true(propDesc.writable, 'method should be writable'); assert_equals(typeof rs[m], 'function', 'method should be a function'); const expectedName = m === 'constructor' ? 'ReadableStream' : m; @@ -65,16 +67,25 @@ test(() => { assert_true(lockedPropDesc.configurable, 'locked should be configurable'); assert_equals(rs.cancel.length, 1, 'cancel should have 1 parameter'); assert_equals(rs.constructor.length, 0, 'constructor should have no parameters'); assert_equals(rs.getReader.length, 0, 'getReader should have no parameters'); assert_equals(rs.pipeThrough.length, 1, 'pipeThrough should have 1 parameters'); assert_equals(rs.pipeTo.length, 1, 'pipeTo should have 1 parameter'); assert_equals(rs.tee.length, 0, 'tee should have no parameters'); + assert_equals(rs.getIterator.length, 0, 'getIterator should have no required parameters'); + assert_equals(rs[Symbol.asyncIterator].length, 0, '@@asyncIterator should have no required parameters'); + + const asyncIteratorPropDesc = Object.getOwnPropertyDescriptor(proto, Symbol.asyncIterator); + assert_false(asyncIteratorPropDesc.enumerable, '@@asyncIterator should be non-enumerable'); + assert_true(asyncIteratorPropDesc.configurable, '@@asyncIterator should be configurable'); + assert_true(asyncIteratorPropDesc.writable, '@@asyncIterator should be writable'); + assert_equals(typeof rs[Symbol.asyncIterator], 'function', '@@asyncIterator should be a function'); + assert_equals(rs[Symbol.asyncIterator].name, 'getIterator', '@@asyncIterator should have the correct name'); }, 'ReadableStream instances should have the correct list of properties'); test(() => { assert_throws(new TypeError(), () => { new ReadableStream({ start: 'potato' }); }, 'constructor should throw when start is not a function');
--- a/testing/web-platform/tests/streams/readable-streams/patched-global.any.js +++ b/testing/web-platform/tests/streams/readable-streams/patched-global.any.js @@ -52,8 +52,58 @@ test(t => { self.ReadableStream = oldReadableStream; }); const [branch1, branch2] = rs.tee(); assert_true(isReadableStream(branch1), 'branch1 should be a ReadableStream'); assert_true(isReadableStream(branch2), 'branch2 should be a ReadableStream'); }, 'ReadableStream tee() should not call the global ReadableStream'); + +promise_test(async t => { + const rs = new ReadableStream({ + start(c) { + c.enqueue(1); + c.enqueue(2); + c.enqueue(3); + c.close(); + } + }); + + const oldReadableStreamGetReader = ReadableStream.prototype.getReader; + + const ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor; + const oldDefaultReaderRead = ReadableStreamDefaultReader.prototype.read; + const oldDefaultReaderCancel = ReadableStreamDefaultReader.prototype.cancel; + const oldDefaultReaderReleaseLock = ReadableStreamDefaultReader.prototype.releaseLock; + + self.ReadableStream.prototype.getReader = function() { + throw new Error('patched getReader() called'); + }; + + ReadableStreamDefaultReader.prototype.read = function() { + throw new Error('patched read() called'); + }; + ReadableStreamDefaultReader.prototype.cancel = function() { + throw new Error('patched cancel() called'); + }; + ReadableStreamDefaultReader.prototype.releaseLock = function() { + throw new Error('patched releaseLock() called'); + }; + + t.add_cleanup(() => { + self.ReadableStream.prototype.getReader = oldReadableStreamGetReader; + + ReadableStreamDefaultReader.prototype.read = oldDefaultReaderRead; + ReadableStreamDefaultReader.prototype.cancel = oldDefaultReaderCancel; + ReadableStreamDefaultReader.prototype.releaseLock = oldDefaultReaderReleaseLock; + }); + + // read the first chunk, then cancel + for await (const chunk of rs) { + break; + } + + // should be able to acquire a new reader + const reader = oldReadableStreamGetReader.call(rs); + // stream should be cancelled + await reader.closed; +}, 'ReadableStream getIterator() should use the original values of getReader() and ReadableStreamDefaultReader methods');