Bug 1630436 [wpt PR 22982] - Streams: update tests for Web IDL conversion, a=testonly

Automatic update from web-platform-tests
Streams: update tests for Web IDL conversion

Follows https://github.com/whatwg/streams/pull/1035. Notable changes:

- Updates for the normative changes listed there.
- Introduce an idlharness test
- Remove various tests for things that are covered by idlharness, such as brand checks, the order of getting values from dictionaries, etc.
- Updated for the fact that everything is now globally exposed, so some roundabout code to get constructors can be removed.
- Slight timing updates: the pattern of returning a promise from an underlying sink/source/transformer `start()` function, then waiting on that before doing asserts, does not work with Web IDL's "promise resolved with". So instead we use `flushAsyncEvents()` to wait a little longer.
- Consolidated queuing strategy tests into a single file, since after deleting the things covered by idlharness, most of the tests are shared.
- Added tests that underlyingSink/underlyingSource are converted after queuingStrategy, since those dictionary conversions are done in prose.
- Added a test for various updates to the Web IDL async iterators specification.
--

wpt-commits: 887350c2f46def5b01c4dd1f8d2eee35dfb9c5bb
wpt-pr: 22982
This commit is contained in:
Domenic Denicola 2020-06-13 03:30:35 +00:00 committed by moz-wptsync-bot
parent 71bad18802
commit 1df360c380
31 changed files with 1000 additions and 1991 deletions

View File

@ -0,0 +1,204 @@
[Exposed=(Window,Worker,Worklet)]
interface ReadableStream {
constructor(optional object underlyingSource, optional QueuingStrategy strategy = {});
readonly attribute boolean locked;
Promise<void> cancel(optional any reason);
ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {});
ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
Promise<void> pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
sequence<ReadableStream> tee();
async iterable<any>(optional ReadableStreamIteratorOptions options = {});
};
typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;
enum ReadableStreamReaderMode { "byob" };
dictionary ReadableStreamGetReaderOptions {
ReadableStreamReaderMode mode;
};
dictionary ReadableStreamIteratorOptions {
boolean preventCancel = false;
};
dictionary ReadableWritablePair {
required ReadableStream readable;
required WritableStream writable;
};
dictionary StreamPipeOptions {
boolean preventClose = false;
boolean preventAbort = false;
boolean preventCancel = false;
AbortSignal signal;
};
dictionary UnderlyingSource {
UnderlyingSourceStartCallback start;
UnderlyingSourcePullCallback pull;
UnderlyingSourceCancelCallback cancel;
ReadableStreamType type;
[EnforceRange] unsigned long long autoAllocateChunkSize;
};
typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController;
callback UnderlyingSourceStartCallback = any (ReadableStreamController controller);
callback UnderlyingSourcePullCallback = Promise<void> (ReadableStreamController controller);
callback UnderlyingSourceCancelCallback = Promise<void> (optional any reason);
enum ReadableStreamType { "bytes" };
[Exposed=(Window,Worker,Worklet)]
interface ReadableStreamDefaultReader {
constructor(ReadableStream stream);
readonly attribute Promise<void> closed;
Promise<void> cancel(optional any reason);
Promise<any> read();
void releaseLock();
};
[Exposed=(Window,Worker,Worklet)]
interface ReadableStreamBYOBReader {
constructor(ReadableStream stream);
readonly attribute Promise<void> closed;
Promise<void> cancel(optional any reason);
Promise<any> read(ArrayBufferView view);
void releaseLock();
};
[Exposed=(Window,Worker,Worklet)]
interface ReadableStreamDefaultController {
readonly attribute unrestricted double? desiredSize;
void close();
void enqueue(optional any chunk);
void error(optional any e);
};
[Exposed=(Window,Worker,Worklet)]
interface ReadableByteStreamController {
readonly attribute ReadableStreamBYOBRequest? byobRequest;
readonly attribute unrestricted double? desiredSize;
void close();
void enqueue(ArrayBufferView chunk);
void error(optional any e);
};
[Exposed=(Window,Worker,Worklet)]
interface ReadableStreamBYOBRequest {
readonly attribute ArrayBufferView? view;
void respond([EnforceRange] unsigned long long bytesWritten);
void respondWithNewView(ArrayBufferView view);
};
[Exposed=(Window,Worker,Worklet)]
interface WritableStream {
constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});
readonly attribute boolean locked;
Promise<void> abort(optional any reason);
Promise<void> close();
WritableStreamDefaultWriter getWriter();
};
dictionary UnderlyingSink {
UnderlyingSinkStartCallback start;
UnderlyingSinkWriteCallback write;
UnderlyingSinkCloseCallback close;
UnderlyingSinkAbortCallback abort;
any type;
};
callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller);
callback UnderlyingSinkWriteCallback = Promise<void> (WritableStreamDefaultController controller, optional any chunk);
callback UnderlyingSinkCloseCallback = Promise<void> ();
callback UnderlyingSinkAbortCallback = Promise<void> (optional any reason);
[Exposed=(Window,Worker,Worklet)]
interface WritableStreamDefaultWriter {
constructor(WritableStream stream);
readonly attribute Promise<void> closed;
readonly attribute unrestricted double? desiredSize;
readonly attribute Promise<void> ready;
Promise<void> abort(optional any reason);
Promise<void> close();
void releaseLock();
Promise<void> write(optional any chunk);
};
[Exposed=(Window,Worker,Worklet)]
interface WritableStreamDefaultController {
void error(optional any e);
};
[Exposed=(Window,Worker,Worklet)]
interface TransformStream {
constructor(optional object transformer,
optional QueuingStrategy writableStrategy = {},
optional QueuingStrategy readableStrategy = {});
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
};
dictionary Transformer {
TransformerStartCallback start;
TransformerTransformCallback transform;
TransformerFlushCallback flush;
any readableType;
any writableType;
};
callback TransformerStartCallback = any (TransformStreamDefaultController controller);
callback TransformerFlushCallback = Promise<void> (TransformStreamDefaultController controller);
callback TransformerTransformCallback = Promise<void> (TransformStreamDefaultController controller, optional any chunk);
[Exposed=(Window,Worker,Worklet)]
interface TransformStreamDefaultController {
readonly attribute unrestricted double? desiredSize;
void enqueue(optional any chunk);
void error(optional any reason);
void terminate();
};
dictionary QueuingStrategy {
unrestricted double highWaterMark;
QueuingStrategySize size;
};
callback QueuingStrategySize = unrestricted double (optional any chunk);
dictionary QueuingStrategyInit {
required unrestricted double highWaterMark;
};
[Exposed=(Window,Worker,Worklet)]
interface ByteLengthQueuingStrategy {
constructor(QueuingStrategyInit init);
attribute unrestricted double highWaterMark;
readonly attribute Function size;
};
[Exposed=(Window,Worker,Worklet)]
interface CountQueuingStrategy {
constructor(QueuingStrategyInit init);
readonly attribute unrestricted double highWaterMark;
readonly attribute Function size;
};

View File

@ -1,132 +0,0 @@
// META: global=window,worker,jsshell
'use strict';
test(() => {
new ByteLengthQueuingStrategy({ highWaterMark: 4 });
}, 'Can construct a ByteLengthQueuingStrategy with a valid high water mark');
test(() => {
for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark });
assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
}
}, 'Can construct a ByteLengthQueuingStrategy with any value as its high water mark');
test(() => {
const highWaterMark = 1;
const highWaterMarkObjectGetter = {
get highWaterMark() { return highWaterMark; }
};
const error = new Error('wow!');
const highWaterMarkObjectGetterThrowing = {
get highWaterMark() { throw error; }
};
assert_throws_js(TypeError, () => new ByteLengthQueuingStrategy(), 'construction fails with undefined');
assert_throws_js(TypeError, () => new ByteLengthQueuingStrategy(null), 'construction fails with null');
assert_throws_js(Error, () => new ByteLengthQueuingStrategy(highWaterMarkObjectGetterThrowing),
'construction fails with an object with a throwing highWaterMark getter');
// Should not fail:
new ByteLengthQueuingStrategy('potato');
new ByteLengthQueuingStrategy({});
new ByteLengthQueuingStrategy(highWaterMarkObjectGetter);
}, 'ByteLengthQueuingStrategy constructor behaves as expected with strange arguments');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
assert_throws_js(TypeError, () => ByteLengthQueuingStrategy.prototype.size(), 'size fails with undefined');
assert_throws_js(TypeError, () => ByteLengthQueuingStrategy.prototype.size(null), 'size fails with null');
assert_equals(ByteLengthQueuingStrategy.prototype.size('potato'), undefined,
'size succeeds with undefined with a random non-object type');
assert_equals(ByteLengthQueuingStrategy.prototype.size({}), undefined,
'size succeeds with undefined with an object without hwm property');
assert_equals(ByteLengthQueuingStrategy.prototype.size(chunk), size,
'size succeeds with the right amount with an object with a hwm');
assert_equals(ByteLengthQueuingStrategy.prototype.size(chunkGetter), size,
'size succeeds with the right amount with an object with a hwm getter');
assert_throws_js(Error, () => ByteLengthQueuingStrategy.prototype.size(chunkGetterThrowing),
'size fails with the error thrown by the getter');
}, 'ByteLengthQueuingStrategy size behaves as expected with strange arguments');
test(() => {
const thisValue = null;
const returnValue = { 'returned from': 'byteLength getter' };
const chunk = {
get byteLength() { return returnValue; }
};
assert_equals(ByteLengthQueuingStrategy.prototype.size.call(thisValue, chunk), returnValue);
}, 'ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments');
test(() => {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
{ value: 4, writable: true, enumerable: true, configurable: true },
'highWaterMark property should be a data property with the value passed the constructor');
assert_equals(typeof strategy.size, 'function');
}, 'ByteLengthQueuingStrategy instances have the correct properties');
test(() => {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
assert_equals(strategy.highWaterMark, 4);
strategy.highWaterMark = 10;
assert_equals(strategy.highWaterMark, 10);
strategy.highWaterMark = 'banana';
assert_equals(strategy.highWaterMark, 'banana');
}, 'ByteLengthQueuingStrategy\'s highWaterMark property can be set to anything');
test(() => {
assert_equals(ByteLengthQueuingStrategy.name, 'ByteLengthQueuingStrategy',
'ByteLengthQueuingStrategy.name must be "ByteLengthQueuingStrategy"');
}, 'ByteLengthQueuingStrategy.name is correct');
class SubClass extends ByteLengthQueuingStrategy {
size() {
return 2;
}
subClassMethod() {
return true;
}
}
test(() => {
const sc = new SubClass({highWaterMark: 77});
assert_equals(sc.constructor.name, 'SubClass',
'constructor.name should be correct');
assert_equals(sc.highWaterMark, 77,
'highWaterMark should come from the parent class');
assert_equals(sc.size(), 2,
'size() on the subclass should override the parent');
assert_true(sc.subClassMethod(), 'subClassMethod() should work');
}, 'subclassing ByteLengthQueuingStrategy should work correctly');

View File

@ -1,131 +0,0 @@
// META: global=window,worker,jsshell
'use strict';
test(() => {
new CountQueuingStrategy({ highWaterMark: 4 });
}, 'Can construct a CountQueuingStrategy with a valid high water mark');
test(() => {
for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
const strategy = new CountQueuingStrategy({ highWaterMark });
assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
}
}, 'Can construct a CountQueuingStrategy with any value as its high water mark');
test(() => {
const highWaterMark = 1;
const highWaterMarkObjectGetter = {
get highWaterMark() { return highWaterMark; }
};
const error = new Error('wow!');
const highWaterMarkObjectGetterThrowing = {
get highWaterMark() { throw error; }
};
assert_throws_js(TypeError, () => new CountQueuingStrategy(), 'construction fails with undefined');
assert_throws_js(TypeError, () => new CountQueuingStrategy(null), 'construction fails with null');
assert_throws_js(Error, () => new CountQueuingStrategy(highWaterMarkObjectGetterThrowing),
'construction fails with an object with a throwing highWaterMark getter');
// Should not fail:
new CountQueuingStrategy('potato');
new CountQueuingStrategy({});
new CountQueuingStrategy(highWaterMarkObjectGetter);
}, 'CountQueuingStrategy constructor behaves as expected with strange arguments');
test(() => {
const thisValue = null;
const chunk = {
get byteLength() {
throw new TypeError('shouldn\'t be called');
}
};
assert_equals(CountQueuingStrategy.prototype.size.call(thisValue, chunk), 1);
}, 'CountQueuingStrategy.prototype.size should work generically on its this and its arguments');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
assert_equals(CountQueuingStrategy.prototype.size(), 1, 'size returns 1 with undefined');
assert_equals(CountQueuingStrategy.prototype.size(null), 1, 'size returns 1 with null');
assert_equals(CountQueuingStrategy.prototype.size('potato'), 1, 'size returns 1 with non-object type');
assert_equals(CountQueuingStrategy.prototype.size({}), 1, 'size returns 1 with empty object');
assert_equals(CountQueuingStrategy.prototype.size(chunk), 1, 'size returns 1 with a chunk');
assert_equals(CountQueuingStrategy.prototype.size(chunkGetter), 1, 'size returns 1 with chunk getter');
assert_equals(CountQueuingStrategy.prototype.size(chunkGetterThrowing), 1,
'size returns 1 with chunk getter that throws');
}, 'CountQueuingStrategy size behaves as expected with strange arguments');
test(() => {
const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
{ value: 4, writable: true, enumerable: true, configurable: true },
'highWaterMark property should be a data property with the value passed the constructor');
assert_equals(typeof strategy.size, 'function');
}, 'CountQueuingStrategy instances have the correct properties');
test(() => {
const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
assert_equals(strategy.highWaterMark, 4);
strategy.highWaterMark = 10;
assert_equals(strategy.highWaterMark, 10);
strategy.highWaterMark = 'banana';
assert_equals(strategy.highWaterMark, 'banana');
}, 'CountQueuingStrategy\'s highWaterMark property can be set to anything');
test(() => {
assert_equals(CountQueuingStrategy.name, 'CountQueuingStrategy',
'CountQueuingStrategy.name must be "CountQueuingStrategy"');
}, 'CountQueuingStrategy.name is correct');
class SubClass extends CountQueuingStrategy {
size() {
return 2;
}
subClassMethod() {
return true;
}
}
test(() => {
const sc = new SubClass({highWaterMark: 77});
assert_equals(sc.constructor.name, 'SubClass',
'constructor.name should be correct');
assert_equals(sc.highWaterMark, 77,
'highWaterMark should come from the parent class');
assert_equals(sc.size(), 2,
'size() on the subclass should override the parent');
assert_true(sc.subClassMethod(), 'subClassMethod() should work');
}, 'subclassing CountQueuingStrategy should work correctly');

View File

@ -0,0 +1,75 @@
// META: global=window,worker
// META: script=/resources/WebIDLParser.js
// META: script=/resources/idlharness.js
// META: timeout=long
idl_test(
['streams'],
['dom'], // for AbortSignal
async idl_array => {
// Empty try/catches ensure that if something isn't implemented (e.g., readable byte streams, or writable streams)
// the harness still sets things up correctly. Note that the corresponding interface tests will still fail.
try {
new ReadableStream({
start(c) {
self.readableStreamDefaultController = c;
}
});
} catch {}
try {
new ReadableStream({
start(c) {
self.readableByteStreamController = c;
},
type: 'bytes'
});
} catch {}
try {
const stream = new ReadableStream({
pull() {
self.readableStreamByobRequest = controller.byobRequest;
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
await reader.read(new Uint8Array(1));
} catch {}
try {
new WritableStream({
start(c) {
self.writableStreamDefaultController = c;
}
});
} catch {}
try {
new TransformStream({
start(c) {
self.transformStreamDefaultController = c;
}
});
} catch {}
idl_array.add_objects({
ReadableStream: ["new ReadableStream()"],
ReadableStreamDefaultReader: ["(new ReadableStream()).getReader()"],
ReadableStreamBYOBReader: ["(new ReadableStream({ type: 'bytes' })).getReader('byob')"],
ReadableStreamDefaultController: ["self.readableStreamDefaultController"],
ReadableByteStreamController: ["self.readableByteStreamController"],
ReadableStreamBYOBRequest: ["self.readableStreamByobRequest"],
WritableStream: ["new WritableStream()"],
WritableStreamDefaultWriter: ["(new WritableStream()).getWriter()"],
WritableStreamDefaultController: ["self.writableStreamDefaultController"],
TransformStream: ["new TransformStream()"],
TransformStreamDefaultController: ["self.transformStreamDefaultController"],
ByteLengthQueuingStrategy: ["new ByteLengthQueuingStrategy({ highWaterMark: 5 })"],
CountQueuingStrategy: ["new CountQueuingStrategy({ highWaterMark: 5 })"]
});
}
);

View File

@ -81,12 +81,8 @@ promise_test(() => {
const rs = recordingReadableStream();
const startPromise = Promise.resolve();
let resolveWritePromise;
const ws = recordingWritableStream({
start() {
return startPromise;
},
write() {
if (!resolveWritePromise) {
// first write
@ -101,7 +97,7 @@ promise_test(() => {
const writer = ws.getWriter();
writer.write('a');
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_array_equals(ws.events, ['write', 'a']);
assert_equals(writer.desiredSize, 0, 'after writing the writer\'s desiredSize must be 0');
writer.releaseLock();

View File

@ -106,17 +106,19 @@ for (const readable of badReadables) {
test(() => {
const rs = new ReadableStream();
const writable = new WritableStream();
let writableGetterCalled = false;
assert_throws_js(TypeError, () => rs.pipeThrough({
get writable() {
writableGetterCalled = true;
return new WritableStream();
},
readable
}),
'pipeThrough should brand-check readable');
assert_true(writableGetterCalled, 'writable should have been accessed');
assert_throws_js(
TypeError,
() => rs.pipeThrough({
get writable() {
writableGetterCalled = true;
return new WritableStream();
},
readable
}),
'pipeThrough should brand-check readable'
);
assert_false(writableGetterCalled, 'writable should not have been accessed');
}, `pipeThrough should brand-check readable and not allow '${readable}'`);
}
@ -150,7 +152,7 @@ test(t => {
}, { highWaterMark: 0 });
const throwingWritable = {
readable: {},
readable: rs,
get writable() {
throw error;
}

View File

@ -1,4 +1,5 @@
// META: global=window,worker,jsshell
'use strict';
class ThrowingOptions {
constructor(whatShouldThrow) {
@ -34,7 +35,7 @@ class ThrowingOptions {
}
}
const checkOrder = ['preventClose', 'preventAbort', 'preventCancel', 'signal'];
const checkOrder = ['preventAbort', 'preventCancel', 'preventClose', 'signal'];
for (let i = 0; i < checkOrder.length; ++i) {
const whatShouldThrow = checkOrder[i];

View File

@ -0,0 +1,135 @@
// META: global=window,worker,jsshell
'use strict';
const highWaterMarkConversions = new Map([
[-Infinity, -Infinity],
[-5, -5],
[false, 0],
[true, 1],
[NaN, NaN],
['foo', NaN],
['0', 0],
[{}, NaN],
[() => {}, NaN]
]);
for (const QueuingStrategy of [CountQueuingStrategy, ByteLengthQueuingStrategy]) {
test(() => {
new QueuingStrategy({ highWaterMark: 4 });
}, `${QueuingStrategy.name}: Can construct a with a valid high water mark`);
test(() => {
const highWaterMark = 1;
const highWaterMarkObjectGetter = {
get highWaterMark() { return highWaterMark; }
};
const error = new Error('wow!');
const highWaterMarkObjectGetterThrowing = {
get highWaterMark() { throw error; }
};
assert_throws_js(TypeError, () => new QueuingStrategy(), 'construction fails with undefined');
assert_throws_js(TypeError, () => new QueuingStrategy(null), 'construction fails with null');
assert_throws_js(TypeError, () => new QueuingStrategy(true), 'construction fails with true');
assert_throws_js(TypeError, () => new QueuingStrategy(5), 'construction fails with 5');
assert_throws_js(TypeError, () => new QueuingStrategy({}), 'construction fails with {}');
assert_throws_exactly(error, () => new QueuingStrategy(highWaterMarkObjectGetterThrowing),
'construction fails with an object with a throwing highWaterMark getter');
assert_equals((new QueuingStrategy(highWaterMarkObjectGetter)).highWaterMark, highWaterMark);
}, `${QueuingStrategy.name}: Constructor behaves as expected with strange arguments`);
test(() => {
for (const [input, output] of highWaterMarkConversions.entries()) {
const strategy = new QueuingStrategy({ highWaterMark: input });
assert_equals(strategy.highWaterMark, output, `${input} gets set correctly`);
}
}, `${QueuingStrategy.name}: highWaterMark constructor values are converted per the unrestricted double rules`);
test(() => {
const size1 = (new QueuingStrategy({ highWaterMark: 5 })).size;
const size2 = (new QueuingStrategy({ highWaterMark: 10 })).size;
assert_equals(size1, size2);
}, `${QueuingStrategy.name}: size is the same function across all instances`);
test(() => {
const size = (new QueuingStrategy({ highWaterMark: 5 })).size;
assert_equals(size.name, 'size');
}, `${QueuingStrategy.name}: size should have the right name`);
test(() => {
class SubClass extends QueuingStrategy {
size() {
return 2;
}
subClassMethod() {
return true;
}
}
const sc = new SubClass({ highWaterMark: 77 });
assert_equals(sc.constructor.name, 'SubClass', 'constructor.name should be correct');
assert_equals(sc.highWaterMark, 77, 'highWaterMark should come from the parent class');
assert_equals(sc.size(), 2, 'size() on the subclass should override the parent');
assert_true(sc.subClassMethod(), 'subClassMethod() should work');
}, `${QueuingStrategy.name}: subclassing should work correctly`);
}
test(() => {
const size = (new CountQueuingStrategy({ highWaterMark: 5 })).size;
assert_equals(size.length, 0);
}, 'CountQueuingStrategy: size should have the right length');
test(() => {
const size = (new ByteLengthQueuingStrategy({ highWaterMark: 5 })).size;
assert_equals(size.length, 1);
}, 'ByteLengthQueuingStrategy: size should have the right length');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
const sizeFunction = (new CountQueuingStrategy({ highWaterMark: 5 })).size;
assert_equals(sizeFunction(), 1, 'size returns 1 with undefined');
assert_equals(sizeFunction(null), 1, 'size returns 1 with null');
assert_equals(sizeFunction('potato'), 1, 'size returns 1 with non-object type');
assert_equals(sizeFunction({}), 1, 'size returns 1 with empty object');
assert_equals(sizeFunction(chunk), 1, 'size returns 1 with a chunk');
assert_equals(sizeFunction(chunkGetter), 1, 'size returns 1 with chunk getter');
assert_equals(sizeFunction(chunkGetterThrowing), 1,
'size returns 1 with chunk getter that throws');
}, 'CountQueuingStrategy: size behaves as expected with strange arguments');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
const sizeFunction = (new ByteLengthQueuingStrategy({ highWaterMark: 5 })).size;
assert_throws_js(TypeError, () => sizeFunction(), 'size fails with undefined');
assert_throws_js(TypeError, () => sizeFunction(null), 'size fails with null');
assert_equals(sizeFunction('potato'), undefined, 'size succeeds with undefined with a random non-object type');
assert_equals(sizeFunction({}), undefined, 'size succeeds with undefined with an object without hwm property');
assert_equals(sizeFunction(chunk), size, 'size succeeds with the right amount with an object with a hwm');
assert_equals(sizeFunction(chunkGetter), size,
'size succeeds with the right amount with an object with a hwm getter');
assert_throws_exactly(error, () => sizeFunction(chunkGetterThrowing),
'size fails with the error thrown by the getter');
}, 'ByteLengthQueuingStrategy: size behaves as expected with strange arguments');

View File

@ -48,16 +48,36 @@ promise_test(() => {
}, 'ReadableStream with byte source: read()ing from a stream with queued chunks still transfers the buffer');
test(() => {
const stream = new ReadableStream({
new ReadableStream({
start(c) {
const view = new Uint8Array([1, 2, 3]);
c.enqueue(view);
assert_throws_js(TypeError, () => c.enqueue(view), 'enqueuing an already-detached buffer must throw');
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing an already-detached buffer throws');
test(() => {
new ReadableStream({
start(c) {
const view = new Uint8Array([]);
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing a zero-length buffer throws');
test(() => {
new ReadableStream({
start(c) {
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing a zero-length view on a non-zero-length buffer throws');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
@ -70,11 +90,36 @@ promise_test(t => {
const view = new Uint8Array([4, 5, 6]);
return reader.read(view).then(() => {
// view is now detached
return promise_rejects_js(t, TypeError, reader.read(view),
'read(view) must reject when given an already-detached buffer');
return promise_rejects_js(t, TypeError, reader.read(view));
});
}, 'ReadableStream with byte source: reading into an already-detached buffer rejects');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array();
return promise_rejects_js(t, TypeError, reader.read(view));
}, 'ReadableStream with byte source: reading into a zero-length buffer rejects');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
return promise_rejects_js(t, TypeError, reader.read(view));
}, 'ReadableStream with byte source: reading into a zero-length view on a non-zero-length buffer rejects');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
@ -118,8 +163,7 @@ async_test(t => {
const view = new Uint8Array([1, 2, 3]);
reader.read(view);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view),
'respondWithNewView() must throw if passed a detached view');
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
@ -129,6 +173,36 @@ async_test(t => {
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array();
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' +
'non-zero-length buffer (in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
@ -139,8 +213,7 @@ async_test(t => {
c.close();
const zeroLengthView = new Uint8Array(view.buffer, 0, 0);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(zeroLengthView),
'respondWithNewView() must throw if passed a (zero-length) view whose buffer has been detached');
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(zeroLengthView));
}),
type: 'bytes'
});
@ -149,3 +222,37 @@ async_test(t => {
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array();
c.close();
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
c.close();
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' +
'non-zero-length buffer (in the closed state)');

View File

@ -1,189 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/test-utils.js
'use strict';
let ReadableStreamBYOBReader;
let ReadableByteStreamController;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamBYOBReader = realRSBYOBReader().constructor;
assert_equals(ReadableStreamBYOBReader.name, 'ReadableStreamBYOBReader', 'ReadableStreamBYOBReader should be set');
}, 'Can get the ReadableStreamBYOBReader constructor indirectly');
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableByteStreamController = realRBSController().constructor;
assert_equals(ReadableByteStreamController.name, 'ReadableByteStreamController',
'ReadableByteStreamController should be set');
}, 'Can get the ReadableByteStreamController constructor indirectly');
function fakeRS() {
return Object.setPrototypeOf({
cancel() { return Promise.resolve(); },
getReader() { return realRSBYOBReader(); },
pipeThrough(obj) { return obj.readable; },
pipeTo() { return Promise.resolve(); },
tee() { return [realRS(), realRS()]; }
}, ReadableStream.prototype);
}
function realRS() {
return new ReadableStream();
}
function fakeRSBYOBReader() {
return Object.setPrototypeOf({
get closed() { return Promise.resolve(); },
cancel() { return Promise.resolve(); },
read() { return Promise.resolve({ value: undefined, done: true }); },
releaseLock() { return; }
}, ReadableStreamBYOBReader.prototype);
}
function realRSBYOBReader() {
return new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
}
function fakeRBSController() {
return Object.setPrototypeOf({
close() { },
enqueue() { },
error() { }
}, ReadableByteStreamController.prototype);
}
function realRBSController() {
let controller;
new ReadableStream({
start(c) {
controller = c;
},
type: 'bytes'
});
return controller;
}
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(fakeRS()), 'constructor should throw');
}, 'ReadableStreamBYOBReader enforces a brand check on its argument');
promise_test(t => {
return getterRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'closed',
[fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
}, 'ReadableStreamBYOBReader.prototype.closed enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'cancel',
[fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
}, 'ReadableStreamBYOBReader.prototype.cancel enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'read',
[fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null], [new Uint8Array(1)]);
}, 'ReadableStreamBYOBReader.prototype.read enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStreamBYOBReader.prototype, 'releaseLock',
[fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
}, 'ReadableStreamBYOBReader.prototype.releaseLock enforces a brand check');
test(() => {
assert_throws_js(TypeError, () => new ReadableByteStreamController(fakeRS()),
'Constructing a ReadableByteStreamController should throw');
}, 'ReadableByteStreamController enforces a brand check on its arguments');
test(() => {
assert_throws_js(TypeError, () => new ReadableByteStreamController(realRS()),
'Constructing a ReadableByteStreamController should throw');
}, 'ReadableByteStreamController can\'t be given a fully-constructed ReadableStream');
test(() => {
getterThrowsForAll(ReadableByteStreamController.prototype, 'byobRequest',
[fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
}, 'ReadableByteStreamController.prototype.byobRequest enforces a brand check');
test(() => {
methodThrowsForAll(ReadableByteStreamController.prototype, 'close',
[fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
}, 'ReadableByteStreamController.prototype.close enforces a brand check');
test(() => {
methodThrowsForAll(ReadableByteStreamController.prototype, 'enqueue',
[fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null], [new Uint8Array(1)]);
}, 'ReadableByteStreamController.prototype.enqueue enforces a brand check');
test(() => {
methodThrowsForAll(ReadableByteStreamController.prototype, 'error',
[fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
}, 'ReadableByteStreamController.prototype.error enforces a brand check');
// ReadableStreamBYOBRequest can only be accessed asynchronously, so cram everything into one test.
promise_test(t => {
let ReadableStreamBYOBRequest;
const rs = new ReadableStream({
pull(controller) {
return t.step(() => {
const byobRequest = controller.byobRequest;
ReadableStreamBYOBRequest = byobRequest.constructor;
brandChecks();
byobRequest.respond(1);
});
},
type: 'bytes'
});
const reader = rs.getReader({ mode: 'byob' });
return reader.read(new Uint8Array(1));
function fakeRSBYOBRequest() {
return Object.setPrototypeOf({
get view() {},
respond() {},
respondWithNewView() {}
}, ReadableStreamBYOBRequest.prototype);
}
function brandChecks() {
for (const badController of [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]) {
assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(badController, new Uint8Array(1)),
'ReadableStreamBYOBRequest constructor must throw for an invalid controller argument');
}
getterThrowsForAll(ReadableStreamBYOBRequest.prototype, 'view',
[fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null]);
methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respond',
[fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null], [1]);
methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respondWithNewView',
[fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null],
[new Uint8Array(1)]);
}
}, 'ReadableStreamBYOBRequest enforces brand checks');

View File

@ -17,8 +17,6 @@ function getRealByteStreamController() {
return controller;
}
const ReadableByteStreamController = getRealByteStreamController().constructor;
// Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null",
// "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object.
function createDummyObject(prototype, type, realObjectCreator) {
@ -41,39 +39,15 @@ function createDummyObject(prototype, type, realObjectCreator) {
const dummyTypes = ['undefined', 'null', 'fake', 'real'];
function runTests(ReadableStreamBYOBRequest) {
for (const controllerType of dummyTypes) {
const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
getRealByteStreamController);
for (const viewType of dummyTypes) {
const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view),
'constructor should throw');
}, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
`ReadableByteStreamController and a ${viewType} view`);
}
for (const controllerType of dummyTypes) {
const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
getRealByteStreamController);
for (const viewType of dummyTypes) {
const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view),
'constructor should throw');
}, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
`ReadableByteStreamController and a ${viewType} view`);
}
}
function getConstructorAndRunTests() {
let ReadableStreamBYOBRequest;
const rs = new ReadableStream({
pull(controller) {
const byobRequest = controller.byobRequest;
ReadableStreamBYOBRequest = byobRequest.constructor;
byobRequest.respond(4);
},
type: 'bytes'
});
rs.getReader({ mode: 'byob' }).read(new Uint8Array(8)).then(() => {
runTests(ReadableStreamBYOBRequest);
done();
});
}
// We can only get at the ReadableStreamBYOBRequest constructor asynchronously, so we need to make the test harness wait
// for us to explicitly tell it all our tests have run.
setup({ explicit_done: true });
getConstructorAndRunTests();

View File

@ -1,48 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/constructor-ordering.js
'use strict';
const operations = [
op('get', 'size'),
op('get', 'highWaterMark'),
op('get', 'type'),
op('validate', 'type'),
op('validate', 'size'),
op('tonumber', 'highWaterMark'),
op('validate', 'highWaterMark'),
op('get', 'pull'),
op('validate', 'pull'),
op('get', 'cancel'),
op('validate', 'cancel'),
op('get', 'autoAllocateChunkSize'),
op('tonumber', 'autoAllocateChunkSize'),
op('validate', 'autoAllocateChunkSize'),
op('get', 'start'),
op('validate', 'start')
];
for (const failureOp of operations) {
test(() => {
const record = new OpRecorder(failureOp);
const underlyingSource = createRecordingObjectWithProperties(record, ['start', 'pull', 'cancel']);
// The valid value for "type" is "bytes", so set it separately.
defineCheckedProperty(record, underlyingSource, 'type', () => record.check('type') ? 'invalid' : 'bytes');
// autoAllocateChunkSize is a special case because it has a tonumber step.
defineCheckedProperty(record, underlyingSource, 'autoAllocateChunkSize',
() => createRecordingNumberObject(record, 'autoAllocateChunkSize'));
const strategy = createRecordingStrategy(record);
try {
new ReadableStream(underlyingSource, strategy);
assert_unreached('constructor should throw');
} catch (e) {
assert_equals(typeof e, 'object', 'e should be an object');
}
assert_equals(record.actual(), expectedAsString(operations, failureOp),
'operations should be performed in the right order');
}, `ReadableStream constructor should stop after ${failureOp} fails`);
}

View File

@ -307,8 +307,8 @@ promise_test(() => {
const byobRequest = controller.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
defined: byobRequest !== undefined,
viewDefined: view !== undefined,
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
@ -337,8 +337,8 @@ promise_test(() => {
return Promise.resolve().then(() => {
assert_equals(pullCount, 1, 'pull() must have been invoked once');
const byobRequest = byobRequests[0];
assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
@ -356,8 +356,8 @@ promise_test(() => {
assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
const byobRequest = byobRequests[1];
assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16');
@ -391,8 +391,8 @@ promise_test(() => {
const byobRequest = controller.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
defined: byobRequest !== undefined,
viewDefined: view !== undefined,
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
@ -422,8 +422,8 @@ promise_test(() => {
assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
const byobRequest = byobRequests[0];
assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
@ -443,8 +443,8 @@ promise_test(() => {
assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
const byobRequest = byobRequests[1];
assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 32, 'second view.buffer.byteLength should be 32');
@ -695,7 +695,7 @@ promise_test(() => {
return reader.read().then(result => {
assert_equals(result.done, false, 'done');
assert_equals(result.value.byteLength, 16, 'byteLength');
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_equals(byobRequest, null, 'byobRequest must be null');
});
}, 'ReadableStream with byte source: Respond to pull() by enqueue()');
@ -745,7 +745,7 @@ promise_test(() => {
assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
assert_equals(result[2].done, false, 'result[2].done');
assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
assert_equals(byobRequest, null, 'byobRequest should be null');
assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0');
assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0');
assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0');
@ -794,7 +794,7 @@ promise_test(() => {
assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
assert_equals(result[2].done, false, 'result[2].done');
assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
assert_equals(byobRequest, null, 'byobRequest should be null');
assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
@ -813,13 +813,13 @@ promise_test(() => {
controller = c;
},
pull() {
byobRequestDefined.push(controller.byobRequest !== undefined);
byobRequestDefined.push(controller.byobRequest !== null);
const view = controller.byobRequest.view;
view[0] = 0x01;
controller.byobRequest.respond(1);
byobRequestDefined.push(controller.byobRequest !== undefined);
byobRequestDefined.push(controller.byobRequest !== null);
++pullCount;
},
@ -833,8 +833,8 @@ promise_test(() => {
assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
assert_equals(result.value[0], 0x01, 'result.value[0]');
assert_equals(pullCount, 1, 'pull() should be called only once');
assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
assert_true(byobRequestDefined[0], 'byobRequest must not be null before respond()');
assert_false(byobRequestDefined[1], 'byobRequest must be null after respond()');
});
}, 'ReadableStream with byte source: read(view), then respond()');
@ -849,7 +849,7 @@ promise_test(() => {
controller = c;
},
pull() {
byobRequestDefined.push(controller.byobRequest !== undefined);
byobRequestDefined.push(controller.byobRequest !== null);
// Emulate ArrayBuffer transfer by just creating a new ArrayBuffer and pass it. By checking the result of
// read(view), we test that the respond()'s buffer argument is working correctly.
@ -860,7 +860,7 @@ promise_test(() => {
transferredView[0] = 0x01;
controller.byobRequest.respondWithNewView(transferredView);
byobRequestDefined.push(controller.byobRequest !== undefined);
byobRequestDefined.push(controller.byobRequest !== null);
++pullCount;
},
@ -874,8 +874,8 @@ promise_test(() => {
assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
assert_equals(result.value[0], 0x01, 'result.value[0]');
assert_equals(pullCount, 1, 'pull() should be called only once');
assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
assert_true(byobRequestDefined[0], 'byobRequest must not be null before respond()');
assert_false(byobRequestDefined[1], 'byobRequest must be null after respond()');
});
}, 'ReadableStream with byte source: read(view), then respond() with a transferred ArrayBuffer');
@ -889,7 +889,7 @@ promise_test(() => {
controller = c;
},
pull() {
byobRequestWasDefined = controller.byobRequest !== undefined;
byobRequestWasDefined = controller.byobRequest !== null;
try {
controller.byobRequest.respond(2);
@ -905,7 +905,7 @@ promise_test(() => {
const reader = stream.getReader({ mode: 'byob' });
return reader.read(new Uint8Array(1)).then(() => {
assert_true(byobRequestWasDefined, 'byobRequest should be defined');
assert_true(byobRequestWasDefined, 'byobRequest should be non-null');
assert_not_equals(incorrectRespondException, undefined, 'respond() must throw');
assert_equals(incorrectRespondException.name, 'RangeError', 'respond() must throw a RangeError');
});
@ -954,7 +954,7 @@ promise_test(() => {
return reader.read(new Uint8Array(1));
}).then(result => {
assert_equals(pullCount, 1);
assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
assert_not_equals(byobRequest, null, 'byobRequest must not be null');
assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4');
assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
@ -1129,7 +1129,7 @@ promise_test(() => {
});
assert_equals(pullCount, 1, '1 pull() should have been made in response to partial fill by enqueue()');
assert_not_equals(byobRequest, undefined, 'byobRequest should not be undefined');
assert_not_equals(byobRequest, null, 'byobRequest should not be null');
assert_equals(viewInfos[0].byteLength, 2, 'byteLength before enqueue() shouild be 2');
assert_equals(viewInfos[1].byteLength, 1, 'byteLength after enqueue() should be 1');
@ -1146,7 +1146,7 @@ promise_test(() => {
promise_test(() => {
let controller;
let byobRequest;
let pullCalled = false;
const stream = new ReadableStream({
start(c) {
@ -1157,7 +1157,7 @@ promise_test(() => {
controller = c;
},
pull() {
byobRequest = controller.byobRequest;
pullCalled = true;
},
type: 'bytes'
});
@ -1169,7 +1169,7 @@ promise_test(() => {
return reader.read(new Uint8Array(buffer, 8, 8)).then(result => {
assert_equals(result.done, false);
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_false(pullCalled, 'pull() must not have been called');
const view = result.value;
assert_equals(view.constructor, Uint8Array);
@ -1183,7 +1183,7 @@ promise_test(() => {
promise_test(() => {
let controller;
let byobRequest;
let pullCalled = false;
const stream = new ReadableStream({
start(c) {
@ -1200,7 +1200,7 @@ promise_test(() => {
controller = c;
},
pull() {
byobRequest = controller.byobRequest;
pullCalled = true;
},
type: 'bytes'
});
@ -1210,7 +1210,7 @@ promise_test(() => {
return reader.read(new Uint8Array(24)).then(result => {
assert_equals(result.done, false, 'done');
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_false(pullCalled, 'pull() must not have been called');
const view = result.value;
assert_equals(view.byteOffset, 0, 'byteOffset');
@ -1221,7 +1221,7 @@ promise_test(() => {
}, 'ReadableStream with byte source: Multiple enqueue(), getReader(), then read(view)');
promise_test(() => {
let byobRequest;
let pullCalled = false;
const stream = new ReadableStream({
start(c) {
@ -1229,8 +1229,8 @@ promise_test(() => {
view[15] = 0x01;
c.enqueue(view);
},
pull(controller) {
byobRequest = controller.byobRequest;
pull() {
pullCalled = true;
},
type: 'bytes'
});
@ -1240,7 +1240,7 @@ promise_test(() => {
return reader.read(new Uint8Array(24)).then(result => {
assert_equals(result.done, false);
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_false(pullCalled, 'pull() must not have been called');
const view = result.value;
assert_equals(view.byteOffset, 0);
@ -1250,7 +1250,7 @@ promise_test(() => {
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a bigger view');
promise_test(() => {
let byobRequest;
let pullCalled = false;
const stream = new ReadableStream({
start(c) {
@ -1259,8 +1259,8 @@ promise_test(() => {
view[15] = 0x02;
c.enqueue(view);
},
pull(controller) {
byobRequest = controller.byobRequest;
pull() {
pullCalled = true;
},
type: 'bytes'
});
@ -1279,14 +1279,14 @@ promise_test(() => {
}).then(result => {
assert_equals(result.done, false, 'done');
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_false(pullCalled, 'pull() must not have been called');
const view = result.value;
assert_equals(view.byteOffset, 0);
assert_equals(view.byteLength, 8);
assert_equals(view[7], 0x02);
});
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a smaller views');
}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with smaller views');
promise_test(() => {
let controller;
@ -1301,7 +1301,7 @@ promise_test(() => {
controller = c;
},
pull() {
if (controller.byobRequest === undefined) {
if (controller.byobRequest === null) {
return;
}
@ -1393,7 +1393,7 @@ promise_test(() => {
assert_equals(view.byteLength, 2, 'byteLength');
assert_equals(view[0], 0x0302, 'Contents are set');
assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
assert_not_equals(byobRequest, null, 'byobRequest must not be null');
assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
@ -1530,7 +1530,7 @@ promise_test(() => {
assert_equals(view.byteOffset, 0);
assert_equals(view.byteLength, 0);
assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
assert_not_equals(byobRequest, null, 'byobRequest must not be null');
assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16');
assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
@ -1549,7 +1549,7 @@ promise_test(() => {
controller = c;
},
pull() {
if (controller.byobRequest === undefined) {
if (controller.byobRequest === null) {
return;
}
@ -1638,7 +1638,7 @@ promise_test(() => {
assert_equals(view.byteOffset, 0);
assert_equals(view.byteLength, 2);
assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
assert_equals(byobRequest, null, 'byobRequest must be null');
});
assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
@ -1760,10 +1760,10 @@ promise_test(t => {
}, 'ReadableStream with byte source: Multiple read(view) and multiple enqueue()');
promise_test(t => {
let byobRequest;
let pullCalled = false;
const stream = new ReadableStream({
pull(controller) {
byobRequest = controller.byobRequest;
pullCalled = true;
},
type: 'bytes'
});
@ -1771,24 +1771,9 @@ promise_test(t => {
const reader = stream.getReader({ mode: 'byob' });
return promise_rejects_js(t, TypeError, reader.read(), 'read() must fail')
.then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
.then(() => assert_false(pullCalled, 'pull() must not have been called'));
}, 'ReadableStream with byte source: read(view) with passing undefined as view must fail');
promise_test(t => {
let byobRequest;
const stream = new ReadableStream({
pull(controller) {
byobRequest = controller.byobRequest;
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
return promise_rejects_js(t, TypeError, reader.read(new Uint8Array(0)), 'read(view) must fail')
.then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
}, 'ReadableStream with byte source: read(view) with zero-length view must fail');
promise_test(t => {
const stream = new ReadableStream({
type: 'bytes'
@ -1898,7 +1883,7 @@ promise_test(t => {
const promise = promise_rejects_exactly(t, testError, reader.read(), 'read() must fail');
return promise_rejects_exactly(t, testError, promise.then(() => reader.closed))
.then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
.then(() => assert_equals(byobRequest, null, 'byobRequest must be null'));
}, 'ReadableStream with byte source: Throwing in pull function must error the stream');
promise_test(t => {
@ -1917,7 +1902,7 @@ promise_test(t => {
return promise_rejects_exactly(t, error1, reader.read(), 'read() must fail')
.then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail'))
.then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
.then(() => assert_equals(byobRequest, null, 'byobRequest must be null'));
}, 'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is ' +
'errored in it');
@ -1938,7 +1923,7 @@ promise_test(t => {
return promise_rejects_exactly(t, testError, reader.read(new Uint8Array(1)), 'read(view) must fail')
.then(() => promise_rejects_exactly(t, testError, reader.closed, 'reader.closed must reject'))
.then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
.then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null'));
}, 'ReadableStream with byte source: Throwing in pull in response to read(view) function must error the stream');
promise_test(t => {
@ -1957,7 +1942,7 @@ promise_test(t => {
return promise_rejects_exactly(t, error1, reader.read(new Uint8Array(1)), 'read(view) must fail')
.then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail'))
.then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
.then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null'));
}, 'ReadableStream with byte source: Throwing in pull in response to read(view) must be ignored if the stream is ' +
'errored in it');
@ -2101,9 +2086,6 @@ test(() => {
}
}), 'constructor should throw for size function');
assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, { size: null }),
'constructor should throw for size defined');
assert_throws_js(RangeError,
() => new ReadableStream({ type: 'bytes' }, new CountQueuingStrategy({ highWaterMark: 1 })),
'constructor should throw when strategy is CountQueuingStrategy');

View File

@ -1,142 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/rs-utils.js
'use strict';
let ReadableStreamBYOBReader;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamBYOBReader = (new ReadableStream({ type: 'bytes' }))
.getReader({ mode: 'byob' }).constructor;
}, 'Can get the ReadableStreamBYOBReader constructor indirectly');
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamBYOBReader('potato'));
assert_throws_js(TypeError, () => new ReadableStreamBYOBReader({}));
assert_throws_js(TypeError, () => new ReadableStreamBYOBReader());
}, 'ReadableStreamBYOBReader constructor should get a ReadableStream object as argument');
test(() => {
const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
const properties = methods.concat(['closed']).sort();
const rsReader = new ReadableStreamBYOBReader(new ReadableStream({ type: 'bytes' }));
const proto = Object.getPrototypeOf(rsReader);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof rsReader[m], 'function', 'should have be a method');
const expectedName = m === 'constructor' ? 'ReadableStreamBYOBReader' : m;
assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
}
const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
assert_equals(rsReader.read.length, 1, 'read has 1 parameter');
assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
}, 'ReadableStreamBYOBReader instances should have the correct list of properties');
promise_test(t => {
const rs = new ReadableStream({
pull(controller) {
return t.step(() => {
const byobRequest = controller.byobRequest;
const methods = ['constructor', 'respond', 'respondWithNewView'];
const properties = methods.concat(['view']).sort();
const proto = Object.getPrototypeOf(byobRequest);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof byobRequest[m], 'function', 'should have a method');
const expectedName = m === 'constructor' ? 'ReadableStreamBYOBRequest' : m;
assert_equals(byobRequest[m].name, expectedName, 'method should have the correct name');
}
const viewPropDesc = Object.getOwnPropertyDescriptor(proto, 'view');
assert_equals(viewPropDesc.enumerable, false, 'view should be non-enumerable');
assert_equals(viewPropDesc.configurable, true, 'view should be configurable');
assert_not_equals(viewPropDesc.get, undefined, 'view should have a getter');
assert_equals(viewPropDesc.set, undefined, 'view should not have a setter');
assert_not_equals(byobRequest.view, undefined, 'has a non-undefined view property');
assert_equals(byobRequest.constructor.length, 0, 'constructor has 0 parameters');
assert_equals(byobRequest.respond.length, 1, 'respond has 1 parameter');
assert_equals(byobRequest.respondWithNewView.length, 1, 'releaseLock has 1 parameter');
byobRequest.respond(1);
});
},
type: 'bytes' });
const reader = rs.getReader({ mode: 'byob' });
return reader.read(new Uint8Array(1));
}, 'ReadableStreamBYOBRequest instances should have the correct list of properties');
test(() => {
const methods = ['close', 'constructor', 'enqueue', 'error'];
const accessors = ['byobRequest', 'desiredSize'];
const properties = methods.concat(accessors).sort();
let controller;
new ReadableStream({
start(c) {
controller = c;
},
type: 'bytes'
});
const proto = Object.getPrototypeOf(controller);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof controller[m], 'function', 'should have be a method');
const expectedName = m === 'constructor' ? 'ReadableByteStreamController' : m;
assert_equals(controller[m].name, expectedName, 'method should have the correct name');
}
for (const a of accessors) {
const propDesc = Object.getOwnPropertyDescriptor(proto, a);
assert_equals(propDesc.enumerable, false, `${a} should be non-enumerable`);
assert_equals(propDesc.configurable, true, `${a} should be configurable`);
assert_not_equals(propDesc.get, undefined, `${a} should have a getter`);
assert_equals(propDesc.set, undefined, `${a} should not have a setter`);
}
assert_equals(controller.close.length, 0, 'cancel has no parameters');
assert_equals(controller.constructor.length, 0, 'constructor has no parameters');
assert_equals(controller.enqueue.length, 1, 'enqueue has 1 parameter');
assert_equals(controller.error.length, 1, 'releaseLock has 1 parameter');
}, 'ReadableByteStreamController instances should have the correct list of properties');

View File

@ -4,13 +4,24 @@
// 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(() => {
assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.getIterator);
}, '@@asyncIterator() method is === to getIterator() method');
assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.values);
}, '@@asyncIterator() method is === to values() method');
test(() => {
const s = new ReadableStream();
const it = s.getIterator();
const it = s.values();
const proto = Object.getPrototypeOf(it);
const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype);
@ -21,7 +32,7 @@ test(() => {
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_false(propDesc.enumerable, 'method should be non-enumerable');
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');
@ -40,7 +51,7 @@ promise_test(async () => {
c.enqueue(2);
c.enqueue(3);
c.close();
},
}
});
const chunks = [];
@ -59,7 +70,7 @@ promise_test(async () => {
c.close();
}
i += 1;
},
}
});
const chunks = [];
@ -69,6 +80,42 @@ promise_test(async () => {
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({
@ -81,27 +128,23 @@ promise_test(async () => {
},
}, new CountQueuingStrategy({ highWaterMark: 0 }));
const it = s.getIterator();
const it = s.values();
assert_array_equals(s.events, []);
const read1 = await it.next();
assert_equals(read1.done, false);
assert_equals(read1.value, 1);
assert_iter_result(read1, 1, false);
assert_array_equals(s.events, ['pull']);
const read2 = await it.next();
assert_equals(read2.done, false);
assert_equals(read2.value, 2);
assert_iter_result(read2, 2, false);
assert_array_equals(s.events, ['pull', 'pull']);
const read3 = await it.next();
assert_equals(read3.done, false);
assert_equals(read3.value, 3);
assert_iter_result(read3, 3, false);
assert_array_equals(s.events, ['pull', 'pull', 'pull']);
const read4 = await it.next();
assert_equals(read4.done, true);
assert_equals(read4.value, undefined);
assert_iter_result(read4, undefined, true);
assert_array_equals(s.events, ['pull', 'pull', 'pull']);
}, 'Async-iterating a pull source manually');
@ -160,8 +203,7 @@ promise_test(async () => {
const reader = s.getReader();
const readResult = await reader.read();
assert_equals(readResult.done, false);
assert_equals(readResult.value, 1);
assert_iter_result(readResult, 1, false);
reader.releaseLock();
const chunks = [];
@ -182,7 +224,7 @@ for (const type of ['throw', 'break', 'return']) {
// 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 })) {
for await (const c of s.values({ preventCancel })) {
if (type === 'throw') {
throw new Error();
} else if (type === 'break') {
@ -214,7 +256,7 @@ for (const preventCancel of [false, true]) {
}
});
const it = s.getIterator({ preventCancel });
const it = s.values({ preventCancel });
await it.return();
if (preventCancel) {
@ -226,33 +268,249 @@ for (const preventCancel of [false, true]) {
}
promise_test(async t => {
const s = new ReadableStream();
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]();
await it.return();
return promise_rejects_js(t, TypeError, it.return(), 'return should reject');
}, 'Calling return() twice rejects');
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({
start(c) {
c.enqueue(0);
c.close();
},
pull(c) {
if (timesPulled === 0) {
c.enqueue(0);
++timesPulled;
} else {
c.error(error1);
}
}
});
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');
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 => {
const s = recordingReadableStream();
const it = s[Symbol.asyncIterator]();
it.next();
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);
}
});
await promise_rejects_js(t, TypeError, it.return(), 'return() should reject');
assert_array_equals(s.events, ['pull']);
}, 'calling return() while there are pending reads rejects');
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({
@ -261,9 +519,9 @@ test(() => {
c.close();
},
});
const it = s.getIterator();
assert_throws_js(TypeError, () => s.getIterator(), 'getIterator() should throw');
}, 'getIterator() throws if there\'s already a lock');
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({
@ -272,7 +530,7 @@ promise_test(async () => {
c.enqueue(2);
c.enqueue(3);
c.close();
},
}
});
const chunks = [];
@ -285,6 +543,34 @@ promise_test(async () => {
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) {
@ -321,7 +607,7 @@ promise_test(async () => {
// read the first two chunks, then release lock
const chunks = [];
for await (const chunk of s.getIterator({preventCancel: true})) {
for await (const chunk of s.values({preventCancel: true})) {
chunks.push(chunk);
if (chunk >= 2) {
break;
@ -331,22 +617,14 @@ promise_test(async () => {
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');
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');
promise_test(async t => {
const rs = new ReadableStream();
const it = rs.getIterator();
await it.return();
return promise_rejects_js(t, TypeError, it.next(), 'next() should reject');
}, 'calling next() after return() should reject');
for (const preventCancel of [false, true]) {
test(() => {
const rs = new ReadableStream();
rs.getIterator({ preventCancel }).return();
rs.values({ preventCancel }).return();
// The test passes if this line doesn't throw.
rs.getReader();
}, `return() should unlock the stream synchronously when preventCancel = ${preventCancel}`);

View File

@ -1,209 +0,0 @@
// META: global=window,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');
test(() => {
// It's not exposed globally, but we test a few of its properties here.
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);
}
function realRS() {
return new ReadableStream();
}
function fakeRSDefaultReader() {
return Object.setPrototypeOf({
get closed() { return Promise.resolve(); },
cancel() { return Promise.resolve(); },
read() { return Promise.resolve({ value: undefined, done: true }); },
releaseLock() { return; }
}, ReadableStreamDefaultReader.prototype);
}
function realRSDefaultReader() {
return new ReadableStream().getReader();
}
function fakeRSDefaultController() {
return Object.setPrototypeOf({
close() { },
enqueue() { },
error() { }
}, ReadableStreamDefaultController.prototype);
}
function realRSDefaultController() {
let controller;
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(() => {
methodThrowsForAll(ReadableStream.prototype, 'getIterator',
[fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
}, 'ReadableStream.prototype.getIterator enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStream.prototype, 'getReader',
[fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
}, 'ReadableStream.prototype.getReader enforces a brand check');
test(() => {
getterThrowsForAll(ReadableStream.prototype, 'locked',
[fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
}, 'ReadableStream.prototype.locked enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStream.prototype, 'tee', [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
}, 'ReadableStream.prototype.tee enforces a brand check');
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamDefaultReader(fakeRS()),
'Constructing a ReadableStreamDefaultReader should throw');
}, 'ReadableStreamDefaultReader enforces a brand check on its argument');
promise_test(t => {
return getterRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'closed',
[fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
}, 'ReadableStreamDefaultReader.prototype.closed enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'cancel',
[fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
}, 'ReadableStreamDefaultReader.prototype.cancel enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'read',
[fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
}, 'ReadableStreamDefaultReader.prototype.read enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStreamDefaultReader.prototype, 'releaseLock',
[fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
}, 'ReadableStreamDefaultReader.prototype.releaseLock enforces a brand check');
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamDefaultController(fakeRS()),
'Constructing a ReadableStreamDefaultController should throw');
}, 'ReadableStreamDefaultController enforces a brand check on its argument');
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamDefaultController(realRS()),
'Constructing a ReadableStreamDefaultController should throw');
}, 'ReadableStreamDefaultController can\'t be given a fully-constructed ReadableStream');
test(() => {
getterThrowsForAll(ReadableStreamDefaultController.prototype, 'desiredSize',
[fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
}, 'ReadableStreamDefaultController.prototype.desiredSize enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStreamDefaultController.prototype, 'close',
[fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
}, 'ReadableStreamDefaultController.prototype.close enforces a brand check');
test(() => {
methodThrowsForAll(ReadableStreamDefaultController.prototype, 'enqueue',
[fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
}, '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');

View File

@ -1,37 +1,17 @@
// META: global=window,worker,jsshell
// META: script=../resources/constructor-ordering.js
'use strict';
const operations = [
op('get', 'size'),
op('get', 'highWaterMark'),
op('get', 'type'),
op('validate', 'type'),
op('validate', 'size'),
op('tonumber', 'highWaterMark'),
op('validate', 'highWaterMark'),
op('get', 'pull'),
op('validate', 'pull'),
op('get', 'cancel'),
op('validate', 'cancel'),
op('get', 'start'),
op('validate', 'start')
];
const error1 = new Error('error1');
error1.name = 'error1';
for (const failureOp of operations) {
test(() => {
const record = new OpRecorder(failureOp);
const underlyingSource = createRecordingObjectWithProperties(record, ['type', 'start', 'pull', 'cancel']);
const strategy = createRecordingStrategy(record);
const error2 = new Error('error2');
error2.name = 'error2';
try {
new ReadableStream(underlyingSource, strategy);
assert_unreached('constructor should throw');
} catch (e) {
assert_equals(typeof e, 'object', 'e should be an object');
}
test(() => {
const underlyingSource = { get start() { throw error1; } };
const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } };
assert_equals(record.actual(), expectedAsString(operations, failureOp),
'operations should be performed in the right order');
}, `ReadableStream constructor should stop after ${failureOp} fails`);
}
// underlyingSource is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer.
// So the queuingStrategy exception should be encountered first.
assert_throws_exactly(error2, () => new ReadableStream(underlyingSource, queuingStrategy));
}, 'underlyingSource argument should be converted after queuingStrategy argument');

View File

@ -2,15 +2,6 @@
// META: script=../resources/rs-utils.js
'use strict';
let ReadableStreamDefaultReader;
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');
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamDefaultReader('potato'));
@ -19,44 +10,6 @@ test(() => {
}, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument');
test(() => {
const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
const properties = methods.concat(['closed']).sort();
const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
const proto = Object.getPrototypeOf(rsReader);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof rsReader[m], 'function', 'should have be a method');
const expectedName = m === 'constructor' ? 'ReadableStreamDefaultReader' : m;
assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
}
const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
assert_equals(typeof rsReader.constructor, 'function', 'has a constructor method');
assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
assert_equals(typeof rsReader.read, 'function', 'has a getReader method');
assert_equals(rsReader.read.length, 0, 'read has no parameters');
assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method');
assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
}, 'ReadableStreamDefaultReader instances should have the correct list of properties');
test(() => {
const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
@ -491,7 +444,7 @@ test(() => {
return '';
}
};
assert_throws_js(RangeError, () => rs.getReader({ mode }), 'getReader() should throw');
assert_throws_js(TypeError, () => rs.getReader({ mode }), 'getReader() should throw');
assert_true(toStringCalled, 'toString() should be called');
}, 'getReader() should call ToString() on mode');

View File

@ -26,64 +26,25 @@ test(() => {
test(() => {
assert_throws_js(RangeError, () => new ReadableStream({ type: null }),
assert_throws_js(TypeError, () => new ReadableStream({ type: null }),
'constructor should throw when the type is null');
assert_throws_js(RangeError, () => new ReadableStream({ type: '' }),
assert_throws_js(TypeError, () => new ReadableStream({ type: '' }),
'constructor should throw when the type is empty string');
assert_throws_js(RangeError, () => new ReadableStream({ type: 'asdf' }),
assert_throws_js(TypeError, () => new ReadableStream({ type: 'asdf' }),
'constructor should throw when the type is asdf');
assert_throws_exactly(error1, () => new ReadableStream({ type: { get toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
assert_throws_exactly(error1, () => new ReadableStream({ type: { toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
assert_throws_exactly(
error1,
() => new ReadableStream({ type: { get toString() { throw error1; } } }),
'constructor should throw when ToString() throws'
);
assert_throws_exactly(
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', '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 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;
assert_equals(rs[m].name, expectedName, 'method should have the correct name');
}
const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked');
assert_false(lockedPropDesc.enumerable, 'locked should be non-enumerable');
assert_equals(lockedPropDesc.writable, undefined, 'locked should not be a data property');
assert_equals(typeof lockedPropDesc.get, 'function', 'locked should have a getter');
assert_equals(lockedPropDesc.set, undefined, 'locked should not have a setter');
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_js(TypeError, () => {
@ -109,38 +70,8 @@ test(() => {
let startCalled = false;
const source = {
start(controller) {
start() {
assert_equals(this, source, 'source is this during start');
const methods = ['close', 'enqueue', 'error', 'constructor'];
const properties = ['desiredSize'].concat(methods).sort();
const proto = Object.getPrototypeOf(controller);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties,
'the controller should have the right properties');
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(typeof controller[m], 'function', `should have a ${m} method`);
assert_false(propDesc.enumerable, m + ' should be non-enumerable');
assert_true(propDesc.configurable, m + ' should be configurable');
assert_true(propDesc.writable, m + ' should be writable');
const expectedName = m === 'constructor' ? 'ReadableStreamDefaultController' : m;
assert_equals(controller[m].name, expectedName, 'method should have the correct name');
}
const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desiredSize');
assert_false(desiredSizePropDesc.enumerable, 'desiredSize should be non-enumerable');
assert_equals(desiredSizePropDesc.writable, undefined, 'desiredSize should not be a data property');
assert_equals(typeof desiredSizePropDesc.get, 'function', 'desiredSize should have a getter');
assert_equals(desiredSizePropDesc.set, undefined, 'desiredSize should not have a setter');
assert_true(desiredSizePropDesc.configurable, 'desiredSize should be configurable');
assert_equals(controller.close.length, 0, 'close should have no parameters');
assert_equals(controller.constructor.length, 0, 'constructor should have no parameters');
assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter');
assert_equals(controller.error.length, 1, 'error should have 1 parameter');
startCalled = true;
}
};
@ -148,7 +79,7 @@ test(() => {
new ReadableStream(source);
assert_true(startCalled);
}, 'ReadableStream start should be called with the proper parameters');
}, 'ReadableStream start should be called with the proper thisArg');
test(() => {
@ -178,7 +109,7 @@ test(() => {
(new ReadableStream()).getReader(undefined);
(new ReadableStream()).getReader({});
(new ReadableStream()).getReader({ mode: undefined, notmode: 'ignored' });
assert_throws_js(RangeError, () => (new ReadableStream()).getReader({ mode: 'potato' }));
assert_throws_js(TypeError, () => (new ReadableStream()).getReader({ mode: 'potato' }));
}, 'default ReadableStream getReader() should only accept mode:undefined');
promise_test(() => {
@ -296,18 +227,14 @@ promise_test(() => {
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
new ReadableStream({
start() {
return startPromise;
},
pull() {
pullCount++;
}
});
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
return delay(10);
}).then(() => {
@ -319,12 +246,8 @@ promise_test(() => {
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start() {
return startPromise;
},
pull(c) {
// Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it.
if (pullCount > 0) {
@ -334,7 +257,7 @@ promise_test(() => {
}
});
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
}).then(() => {
const reader = rs.getReader();
@ -351,12 +274,10 @@ promise_test(() => {
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
return startPromise;
},
pull() {
pullCount++;
@ -366,7 +287,7 @@ promise_test(() => {
const read = rs.getReader().read();
assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
return read;
}).then(r => {
@ -387,14 +308,13 @@ promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
return startPromise;
},
pull() {
pullCount++;
}
});
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full');
const read = rs.getReader().read();
@ -413,12 +333,10 @@ promise_test(() => {
let pullCount = 0;
let controller;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
controller = c;
return startPromise;
},
pull() {
++pullCount;
@ -426,7 +344,7 @@ promise_test(() => {
});
const reader = rs.getReader();
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
controller.enqueue('a');
@ -446,12 +364,10 @@ promise_test(() => {
let pullCount = 0;
let controller;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
controller = c;
return startPromise;
},
pull() {
++pullCount;
@ -460,7 +376,7 @@ promise_test(() => {
const reader = rs.getReader();
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
controller.enqueue('a');
@ -484,12 +400,8 @@ promise_test(() => {
let resolve;
let returnedPromise;
let timesCalled = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start() {
return startPromise;
},
pull(c) {
c.enqueue(++timesCalled);
returnedPromise = new Promise(r => resolve = r);
@ -498,9 +410,8 @@ promise_test(() => {
});
const reader = rs.getReader();
return startPromise.then(() => {
return reader.read();
}).then(result1 => {
return reader.read()
.then(result1 => {
assert_equals(timesCalled, 1,
'pull should have been called once after start, but not yet have been called a second time');
assert_object_equals(result1, { value: 1, done: false }, 'read() should fulfill with the enqueued value');
@ -521,7 +432,6 @@ promise_test(() => {
promise_test(() => {
let timesCalled = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream(
{
@ -529,7 +439,6 @@ promise_test(() => {
c.enqueue('a');
c.enqueue('b');
c.enqueue('c');
return startPromise;
},
pull() {
++timesCalled;
@ -544,7 +453,7 @@ promise_test(() => {
);
const reader = rs.getReader();
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
return reader.read();
}).then(result1 => {
assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');

View File

@ -106,7 +106,8 @@ promise_test(async t => {
const reader = oldReadableStreamGetReader.call(rs);
// stream should be cancelled
await reader.closed;
}, 'ReadableStream getIterator() should use the original values of getReader() and ReadableStreamDefaultReader methods');
}, 'ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader ' +
'methods');
test(t => {
const oldPromiseThen = Promise.prototype.then;

View File

@ -1,129 +0,0 @@
'use strict';
// Helpers for tests that constructors perform getting and validation of properties in the standard order.
// See ../readable-streams/constructor.js for an example of how to use them.
// Describes an operation on a property. |type| is "get", "validate" or "tonumber". |name| is the name of the property
// in question. |side| is usually undefined, but is used by TransformStream to distinguish between the readable and
// writable strategies.
class Op {
constructor(type, name, side) {
this.type = type;
this.name = name;
this.side = side;
}
toString() {
return this.side === undefined ? `${this.type} on ${this.name}` : `${this.type} on ${this.name} (${this.side})`;
}
equals(otherOp) {
return this.type === otherOp.type && this.name === otherOp.name && this.side === otherOp.side;
}
}
// Provides a concise syntax to create an Op object. |side| is used by TransformStream to distinguish between the two
// strategies.
function op(type, name, side = undefined) {
return new Op(type, name, side);
}
// Records a sequence of operations. Also checks each operation against |failureOp| to see if it should fail.
class OpRecorder {
constructor(failureOp) {
this.ops = [];
this.failureOp = failureOp;
this.matched = false;
}
// Record an operation. Returns true if this operation should fail.
recordAndCheck(type, name, side = undefined) {
const recordedOp = op(type, name, side);
this.ops.push(recordedOp);
return this.failureOp.equals(recordedOp);
}
// Returns true if validation of this property should fail.
check(name, side = undefined) {
return this.failureOp.equals(op('validate', name, side));
}
// Returns the sequence of recorded operations as a string.
actual() {
return this.ops.toString();
}
}
// Creates an object with the list of properties named in |properties|. Every property access will be recorded in
// |record|, which will also be used to determine whether a particular property access should fail, or whether it should
// return an invalid value that will fail validation.
function createRecordingObjectWithProperties(record, properties) {
const recordingObject = {};
for (const property of properties) {
defineCheckedProperty(record, recordingObject, property, () => record.check(property) ? 'invalid' : undefined);
}
return recordingObject;
}
// Add a getter to |object| named |property| which throws if op('get', property) should fail, and otherwise calls
// getter() to get the return value.
function defineCheckedProperty(record, object, property, getter) {
Object.defineProperty(object, property, {
get() {
if (record.recordAndCheck('get', property)) {
throw new Error(`intentional failure of get ${property}`);
}
return getter();
}
});
}
// Similar to createRecordingObjectWithProperties(), but with specific functionality for "highWaterMark" so that numeric
// conversion can be recorded. Permits |side| to be specified so that TransformStream can distinguish between its two
// strategies.
function createRecordingStrategy(record, side = undefined) {
return {
get size() {
if (record.recordAndCheck('get', 'size', side)) {
throw new Error(`intentional failure of get size`);
}
return record.check('size', side) ? 'invalid' : undefined;
},
get highWaterMark() {
if (record.recordAndCheck('get', 'highWaterMark', side)) {
throw new Error(`intentional failure of get highWaterMark`);
}
return createRecordingNumberObject(record, 'highWaterMark', side);
}
};
}
// Creates an object which will record when it is converted to a number. It will assert if the conversion is to some
// other type, and will fail if op('tonumber', property, side) is set as the failure step. The object will convert to -1
// if 'validate' is set as the failure step, and 1 otherwise.
function createRecordingNumberObject(record, property, side = undefined) {
return {
[Symbol.toPrimitive](hint) {
assert_equals(hint, 'number', `hint for ${property} should be 'number'`);
if (record.recordAndCheck('tonumber', property, side)) {
throw new Error(`intentional failure of ${op('tonumber', property, side)}`);
}
return record.check(property, side) ? -1 : 1;
}
};
}
// Creates a string from everything in |operations| up to and including |failureOp|. "validate" steps are excluded from
// the output, as we cannot record them except by making them fail.
function expectedAsString(operations, failureOp) {
const expected = [];
for (const step of operations) {
if (step.type !== 'validate') {
expected.push(step);
}
if (step.equals(failureOp)) {
break;
}
}
return expected.toString();
}

View File

@ -23,10 +23,13 @@ self.templatedRSEmpty = (label, factory) => {
test(() => {
const rs = factory();
assert_throws_js(RangeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw');
assert_throws_js(RangeError, () => rs.getReader({ mode: null }), 'null mode should throw');
assert_throws_js(RangeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw');
assert_throws_js(TypeError, () => rs.getReader(null), 'null should throw');
assert_throws_js(TypeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw');
assert_throws_js(TypeError, () => rs.getReader({ mode: null }), 'null mode should throw');
assert_throws_js(TypeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw');
assert_throws_js(TypeError, () => rs.getReader(5), '5 should throw');
// Should not throw
rs.getReader(null);
}, label + ': calling getReader with invalid arguments should throw appropriate errors');
};

View File

@ -1,74 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/test-utils.js
'use strict';
const TransformStreamDefaultController = getTransformStreamDefaultControllerConstructor();
function getTransformStreamDefaultControllerConstructor() {
return realTSDefaultController().constructor;
}
function fakeTS() {
return Object.setPrototypeOf({
get readable() { return new ReadableStream(); },
get writable() { return new WritableStream(); }
}, TransformStream.prototype);
}
function realTS() {
return new TransformStream();
}
function fakeTSDefaultController() {
return Object.setPrototypeOf({
get desiredSize() { return 1; },
enqueue() { },
close() { },
error() { }
}, TransformStreamDefaultController.prototype);
}
function realTSDefaultController() {
let controller;
new TransformStream({
start(c) {
controller = c;
}
});
return controller;
}
test(() => {
getterThrowsForAll(TransformStream.prototype, 'readable',
[fakeTS(), realTSDefaultController(), undefined, null]);
}, 'TransformStream.prototype.readable enforces a brand check');
test(() => {
getterThrowsForAll(TransformStream.prototype, 'writable',
[fakeTS(), realTSDefaultController(), undefined, null]);
}, 'TransformStream.prototype.writable enforces a brand check');
test(() => {
constructorThrowsForAll(TransformStreamDefaultController,
[fakeTS(), realTS(), realTSDefaultController(), undefined, null]);
}, 'TransformStreamDefaultConstructor enforces a brand check and doesn\'t permit independent construction');
test(() => {
getterThrowsForAll(TransformStreamDefaultController.prototype, 'desiredSize',
[fakeTSDefaultController(), realTS(), undefined, null]);
}, 'TransformStreamDefaultController.prototype.desiredSize enforces a brand check');
test(() => {
methodThrowsForAll(TransformStreamDefaultController.prototype, 'enqueue',
[fakeTSDefaultController(), realTS(), undefined, null]);
}, 'TransformStreamDefaultController.prototype.enqueue enforces a brand check');
test(() => {
methodThrowsForAll(TransformStreamDefaultController.prototype, 'terminate',
[fakeTSDefaultController(), realTS(), undefined, null]);
}, 'TransformStreamDefaultController.prototype.terminate enforces a brand check');
test(() => {
methodThrowsForAll(TransformStreamDefaultController.prototype, 'error',
[fakeTSDefaultController(), realTS(), undefined, null]);
}, 'TransformStreamDefaultController.prototype.error enforces a brand check');

View File

@ -1,46 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/constructor-ordering.js
'use strict';
const operations = [
op('get', 'size', 'writable'),
op('get', 'highWaterMark', 'writable'),
op('get', 'size', 'readable'),
op('get', 'highWaterMark', 'readable'),
op('get', 'writableType'),
op('validate', 'writableType'),
op('validate', 'size', 'writable'),
op('tonumber', 'highWaterMark', 'writable'),
op('validate', 'highWaterMark', 'writable'),
op('get', 'readableType'),
op('validate', 'readableType'),
op('validate', 'size', 'readable'),
op('tonumber', 'highWaterMark', 'readable'),
op('validate', 'highWaterMark', 'readable'),
op('get', 'transform'),
op('validate', 'transform'),
op('get', 'flush'),
op('validate', 'flush'),
op('get', 'start'),
op('validate', 'start')
];
for (const failureOp of operations) {
test(() => {
const record = new OpRecorder(failureOp);
const transformer = createRecordingObjectWithProperties(
record, ['readableType', 'writableType', 'start', 'transform', 'flush']);
const writableStrategy = createRecordingStrategy(record, 'writable');
const readableStrategy = createRecordingStrategy(record, 'readable');
try {
new TransformStream(transformer, writableStrategy, readableStrategy);
assert_unreached('constructor should throw');
} catch (e) {
assert_equals(typeof e, 'object', 'e should be an object');
}
assert_equals(record.actual(), expectedAsString(operations, failureOp),
'operations should be performed in the right order');
}, `TransformStream constructor should stop after ${failureOp} fails`);
}

View File

@ -12,31 +12,6 @@ test(() => {
new TransformStream({});
}, 'TransformStream can be constructed with no transform function');
test(() => {
const ts = new TransformStream({ transform() { } });
const proto = Object.getPrototypeOf(ts);
const writableStream = Object.getOwnPropertyDescriptor(proto, 'writable');
assert_true(writableStream !== undefined, 'it has a writable property');
assert_false(writableStream.enumerable, 'writable should be non-enumerable');
assert_equals(typeof writableStream.get, 'function', 'writable should have a getter');
assert_equals(writableStream.set, undefined, 'writable should not have a setter');
assert_true(writableStream.configurable, 'writable should be configurable');
assert_true(ts.writable instanceof WritableStream, 'writable is an instance of WritableStream');
assert_not_equals(WritableStream.prototype.getWriter.call(ts.writable), undefined,
'writable should pass WritableStream brand check');
const readableStream = Object.getOwnPropertyDescriptor(proto, 'readable');
assert_true(readableStream !== undefined, 'it has a readable property');
assert_false(readableStream.enumerable, 'readable should be non-enumerable');
assert_equals(typeof readableStream.get, 'function', 'readable should have a getter');
assert_equals(readableStream.set, undefined, 'readable should not have a setter');
assert_true(readableStream.configurable, 'readable should be configurable');
assert_true(ts.readable instanceof ReadableStream, 'readable is an instance of ReadableStream');
assert_not_equals(ReadableStream.prototype.getReader.call(ts.readable), undefined,
'readable should pass ReadableStream brand check');
}, 'TransformStream instances must have writable and readable properties of the correct types');
test(() => {
const ts = new TransformStream({ transform() { } });
@ -44,7 +19,6 @@ test(() => {
assert_equals(writer.desiredSize, 1, 'writer.desiredSize should be 1');
}, 'TransformStream writable starts in the writable state');
promise_test(() => {
const ts = new TransformStream();

View File

@ -1,119 +1,6 @@
// META: global=window,worker,jsshell
'use strict';
// The purpose of this file is to test for objects, attributes and arguments that should not exist.
// The test cases are generated from data tables to reduce duplication.
// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
function IsConstructor(o) {
try {
new new Proxy(o, { construct: () => ({}) })();
return true;
} catch (e) {
return false;
}
}
test(() => {
assert_equals(self['TransformStreamDefaultController'], undefined,
`TransformStreamDefaultController should not be defined`);
}, `TransformStreamDefaultController should not be exported on the global object`);
// Now get hold of the symbol so we can test its properties.
self.TransformStreamDefaultController = (() => {
let controller;
new TransformStream({
start(c) {
controller = c;
}
});
return controller.constructor;
})();
const expected = {
TransformStream: {
constructor: {
type: 'constructor',
length: 0
},
readable: {
type: 'getter'
},
writable: {
type: 'getter'
}
},
TransformStreamDefaultController: {
constructor: {
type: 'constructor',
length: 0
},
desiredSize: {
type: 'getter'
},
enqueue: {
type: 'method',
length: 1
},
error: {
type: 'method',
length: 1
},
terminate: {
type: 'method',
length: 0
}
}
};
for (const c in expected) {
const properties = expected[c];
const prototype = self[c].prototype;
for (const name in properties) {
const fullName = `${c}.prototype.${name}`;
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
test(() => {
const { configurable, enumerable } = descriptor;
assert_true(configurable, `${name} should be configurable`);
assert_false(enumerable, `${name} should not be enumerable`);
}, `${fullName} should have standard properties`);
const type = properties[name].type;
switch (type) {
case 'getter':
test(() => {
const { writable, get, set } = descriptor;
assert_equals(writable, undefined, `${name} should not be a data descriptor`);
assert_equals(typeof get, 'function', `${name} should have a getter`);
assert_equals(set, undefined, `${name} should not have a setter`);
}, `${fullName} should be a getter`);
break;
case 'constructor':
case 'method':
test(() => {
assert_true(descriptor.writable, `${name} should be writable`);
assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
assert_equals(prototype[name].length, properties[name].length,
`${name} should take ${properties[name].length} arguments`);
if (type === 'constructor') {
assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
} else {
assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
}
}, `${fullName} should be a ${type}`);
break;
}
}
test(() => {
const expectedPropertyNames = Object.keys(properties).sort();
const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
assert_array_equals(actualPropertyNames, expectedPropertyNames,
`${c} properties should match expected properties`);
}, `${c}.prototype should have exactly the expected properties`);
}
const transformerMethods = {
start: {
length: 1,
@ -159,31 +46,4 @@ for (const method in transformerMethods) {
assert_true(methodWasCalled, `${method} should be called`);
});
}, `transformer method ${method} should be called even when it's located on the prototype chain`);
promise_test(t => {
const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
'apply', 'construct'];
const touchedProperties = [];
const handler = {
get: t.step_func((target, property) => {
touchedProperties.push(property);
if (property === 'readableType' || property === 'writableType') {
return undefined;
}
return () => Promise.resolve();
})
};
for (const trap of unreachedTraps) {
handler[trap] = t.unreached_func(`${trap} should not be trapped`);
}
const transformer = new Proxy({}, handler);
const ts = new TransformStream(transformer, undefined, { highWaterMark: Infinity });
assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
'expected properties should be got');
return trigger(ts).then(() => {
assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
'no properties should be accessed on method call');
});
}, `unexpected properties should not be accessed when calling transformer method ${method}`);
}

View File

@ -362,7 +362,7 @@ promise_test(t => {
write() {
return flushAsyncEvents();
}
}, new CountQueuingStrategy(4));
}, new CountQueuingStrategy({ highWaterMark: 4 }));
const writer = ws.getWriter();
return writer.ready.then(() => {
const settlementOrder = [];
@ -382,7 +382,7 @@ promise_test(t => {
write() {
return Promise.reject(error1);
}
}, new CountQueuingStrategy(4));
}, new CountQueuingStrategy({ highWaterMark: 4 }));
const writer = ws.getWriter();
return writer.ready.then(() => {
const settlementOrder = [];

View File

@ -102,12 +102,8 @@ promise_test(t => {
promise_test(t => {
const startPromise = Promise.resolve();
let rejectSinkWritePromise;
const ws = recordingWritableStream({
start() {
return startPromise;
},
write() {
return new Promise((r, reject) => {
rejectSinkWritePromise = reject;
@ -115,7 +111,7 @@ promise_test(t => {
}
});
return startPromise.then(() => {
return flushAsyncEvents().then(() => {
const writer = ws.getWriter();
const writePromise = writer.write('a');
rejectSinkWritePromise(error1);

View File

@ -1,120 +0,0 @@
// META: global=window,worker,jsshell
// META: script=../resources/test-utils.js
'use strict';
const WritableStreamDefaultWriter = new WritableStream().getWriter().constructor;
const WriterProto = WritableStreamDefaultWriter.prototype;
const WritableStreamDefaultController = getWritableStreamDefaultControllerConstructor();
function getWritableStreamDefaultControllerConstructor() {
return realWSDefaultController().constructor;
}
function fakeWS() {
return Object.setPrototypeOf({
get locked() { return false; },
abort() { return Promise.resolve(); },
close() { return Promise.resolve(); },
getWriter() { return fakeWSDefaultWriter(); }
}, WritableStream.prototype);
}
function realWS() {
return new WritableStream();
}
function fakeWSDefaultWriter() {
return Object.setPrototypeOf({
get closed() { return Promise.resolve(); },
get desiredSize() { return 1; },
get ready() { return Promise.resolve(); },
abort() { return Promise.resolve(); },
close() { return Promise.resolve(); },
write() { return Promise.resolve(); }
}, WritableStreamDefaultWriter.prototype);
}
function realWSDefaultWriter() {
const ws = new WritableStream();
return ws.getWriter();
}
function fakeWSDefaultController() {
return Object.setPrototypeOf({
error() { return Promise.resolve(); }
}, WritableStreamDefaultController.prototype);
}
function realWSDefaultController() {
let controller;
new WritableStream({
start(c) {
controller = c;
}
});
return controller;
}
test(() => {
getterThrowsForAll(WritableStream.prototype, 'locked',
[fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
}, 'WritableStream.prototype.locked enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, WritableStream.prototype, 'abort',
[fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
}, 'WritableStream.prototype.abort enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, WritableStream.prototype, 'close',
[fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
}, 'WritableStream.prototype.close enforces a brand check');
test(() => {
methodThrowsForAll(WritableStream.prototype, 'getWriter',
[fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
}, 'WritableStream.prototype.getWriter enforces a brand check');
test(() => {
assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(fakeWS()), 'constructor should throw');
}, 'WritableStreamDefaultWriter constructor enforces a brand check');
test(() => {
getterThrowsForAll(WriterProto, 'desiredSize',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.desiredSize enforces a brand check');
promise_test(t => {
return getterRejectsForAll(t, WriterProto, 'closed',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.closed enforces a brand check');
promise_test(t => {
return getterRejectsForAll(t, WriterProto, 'ready',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.ready enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, WriterProto, 'abort',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.abort enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, WriterProto, 'write',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.write enforces a brand check');
promise_test(t => {
return methodRejectsForAll(t, WriterProto, 'close',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.close enforces a brand check');
test(() => {
methodThrowsForAll(WriterProto, 'releaseLock',
[fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
}, 'WritableStreamDefaultWriter.prototype.releaseLock enforces a brand check');
test(() => {
methodThrowsForAll(WritableStreamDefaultController.prototype, 'error',
[fakeWSDefaultController(), realWS(), realWSDefaultWriter(), undefined, null]);
}, 'WritableStreamDefaultController.prototype.error enforces a brand check');

View File

@ -1,10 +1,12 @@
// META: global=window,worker,jsshell
// META: script=../resources/constructor-ordering.js
'use strict';
const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';
promise_test(() => {
let controller;
const ws = new WritableStream({
@ -85,6 +87,15 @@ test(() => {
new WritableStream();
}, 'WritableStream should be constructible with no arguments');
test(() => {
const underlyingSink = { get start() { throw error1; } };
const queuingStrategy = { highWaterMark: 0, get size() { throw error2; } };
// underlyingSink is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer.
// So the queuingStrategy exception should be encountered first.
assert_throws_exactly(error2, () => new WritableStream(underlyingSink, queuingStrategy));
}, 'underlyingSink argument should be converted after queuingStrategy argument');
test(() => {
const ws = new WritableStream({});
@ -102,11 +113,6 @@ test(() => {
assert_equals(typeof writer.closed.then, 'function', 'closed property should be thenable');
}, 'WritableStream instances should have standard methods and properties');
test(() => {
['WritableStreamDefaultWriter', 'WritableStreamDefaultController'].forEach(c =>
assert_equals(typeof self[c], 'undefined', `${c} should not be exported`));
}, 'private constructors should not be exported');
test(() => {
let WritableStreamDefaultController;
new WritableStream({
@ -147,39 +153,3 @@ test(() => {
assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(stream),
'constructor should throw a TypeError exception');
}, 'WritableStreamDefaultWriter constructor should throw when stream argument is locked');
const operations = [
op('get', 'size'),
op('get', 'highWaterMark'),
op('get', 'type'),
op('validate', 'type'),
op('validate', 'size'),
op('tonumber', 'highWaterMark'),
op('validate', 'highWaterMark'),
op('get', 'write'),
op('validate', 'write'),
op('get', 'close'),
op('validate', 'close'),
op('get', 'abort'),
op('validate', 'abort'),
op('get', 'start'),
op('validate', 'start')
];
for (const failureOp of operations) {
test(() => {
const record = new OpRecorder(failureOp);
const underlyingSink = createRecordingObjectWithProperties(record, ['type', 'start', 'write', 'close', 'abort']);
const strategy = createRecordingStrategy(record);
try {
new WritableStream(underlyingSink, strategy);
assert_unreached('constructor should throw');
} catch (e) {
assert_equals(typeof e, 'object', 'e should be an object');
}
assert_equals(record.actual(), expectedAsString(operations, failureOp),
'operations should be performed in the right order');
}, `WritableStream constructor should stop after ${failureOp} fails`);
}

View File

@ -1,150 +1,6 @@
// META: global=window,worker,jsshell
'use strict';
// The purpose of this file is to test for objects, attributes and arguments that should not exist.
// The test cases are generated from data tables to reduce duplication.
// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
function IsConstructor(o) {
try {
new new Proxy(o, { construct: () => ({}) })();
return true;
} catch (e) {
return false;
}
}
for (const func of ['WritableStreamDefaultController', 'WritableStreamDefaultWriter']) {
test(() => {
assert_equals(self[func], undefined, `${func} should not be defined`);
}, `${func} should not be exported on the global object`);
}
// Now get hold of the symbols so we can test their properties.
self.WritableStreamDefaultController = (() => {
let controller;
new WritableStream({
start(c) {
controller = c;
}
});
return controller.constructor;
})();
self.WritableStreamDefaultWriter = new WritableStream().getWriter().constructor;
const expected = {
WritableStream: {
constructor: {
type: 'constructor',
length: 0
},
locked: {
type: 'getter'
},
abort: {
type: 'method',
length: 1
},
close: {
type: 'method',
length: 0
},
getWriter: {
type: 'method',
length: 0
}
},
WritableStreamDefaultController: {
constructor: {
type: 'constructor',
length: 0
},
error: {
type: 'method',
length: 1
}
},
WritableStreamDefaultWriter: {
constructor: {
type: 'constructor',
length: 1
},
closed: {
type: 'getter'
},
desiredSize: {
type: 'getter'
},
ready: {
type: 'getter'
},
abort: {
type: 'method',
length: 1
},
close: {
type: 'method',
length: 0
},
releaseLock: {
type: 'method',
length: 0
},
write: {
type: 'method',
length: 1
}
}
};
for (const c in expected) {
const properties = expected[c];
const prototype = self[c].prototype;
for (const name in properties) {
const fullName = `${c}.prototype.${name}`;
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
test(() => {
const { configurable, enumerable } = descriptor;
assert_true(configurable, `${name} should be configurable`);
assert_false(enumerable, `${name} should not be enumerable`);
}, `${fullName} should have standard properties`);
const type = properties[name].type;
switch (type) {
case 'getter':
test(() => {
const { writable, get, set } = descriptor;
assert_equals(writable, undefined, `${name} should not be a data descriptor`);
assert_equals(typeof get, 'function', `${name} should have a getter`);
assert_equals(set, undefined, `${name} should not have a setter`);
}, `${fullName} should be a getter`);
break;
case 'constructor':
case 'method':
test(() => {
assert_true(descriptor.writable, `${name} should be writable`);
assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
assert_equals(prototype[name].length, properties[name].length,
`${name} should take ${properties[name].length} arguments`);
if (type === 'constructor') {
assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
} else {
assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
}
}, `${fullName} should be a ${type}`);
break;
}
}
test(() => {
const expectedPropertyNames = Object.keys(properties).sort();
const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
assert_array_equals(actualPropertyNames, expectedPropertyNames,
`${c} properties should match expected properties`);
}, `${c}.prototype should have exactly the expected properties`);
}
const sinkMethods = {
start: {
length: 1,
@ -194,31 +50,4 @@ for (const method in sinkMethods) {
assert_true(methodWasCalled, `${method} should be called`);
});
}, `sink method ${method} should be called even when it's located on the prototype chain`);
promise_test(t => {
const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
'apply', 'construct'];
const touchedProperties = [];
const handler = {
get: t.step_func((target, property) => {
touchedProperties.push(property);
if (property === 'type') {
return undefined;
}
return () => Promise.resolve();
})
};
for (const trap of unreachedTraps) {
handler[trap] = t.unreached_func(`${trap} should not be trapped`);
}
const sink = new Proxy({}, handler);
const ws = new WritableStream(sink);
assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
'expected properties should be got');
return trigger(ws.getWriter()).then(() => {
assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
'no properties should be accessed on method call');
});
}, `unexpected properties should not be accessed when calling sink method ${method}`);
}