[Streams API] Sync tests with spec
authorcalvaris@igalia.com <calvaris@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 16 Jun 2015 08:19:04 +0000 (08:19 +0000)
committercalvaris@igalia.com <calvaris@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 16 Jun 2015 08:19:04 +0000 (08:19 +0000)
https://bugs.webkit.org/show_bug.cgi?id=145839

Reviewed by Darin Adler.

Considered all changes in the spec tests and brought them to WebKit.

Some of those changes were related to spec changes, like the strategy or some of the algorithms. Some like the
tee ones were newly added as they will be implemented soon. There are also some tests that shuffled around
because they were just moved at the spec or integrated from the changes we submitted, which was substantially
important as it allowed us to remove some of our custom tests that are now covered by the spec ones.

* resources/gc.js: Added warning about using gcRec, results might not be reliable.
* streams/readable-stream-controller-error-expected.txt:
* streams/readable-stream-controller-error.html: New failing test due to discrepancies with the reference
implementation. Something we will address.
* streams/readable-stream-expected.txt: Removed.
* streams/readable-stream-gc.html: Renumbered.
* streams/readable-stream-reader-expected.txt: Removed.
* streams/readable-stream-reader-read.html: Renumbered.
* streams/readable-stream-reader.html: Removed.
* streams/readable-stream.html: Removed.
* streams/reference-implementation/bad-strategies-expected.txt: Added.
* streams/reference-implementation/bad-strategies.html: Added.
* streams/reference-implementation/bad-underlying-sources-expected.txt:
* streams/reference-implementation/bad-underlying-sources.html: Updated from spec and expectations.
* streams/reference-implementation/brand-checks-expected.txt:
* streams/reference-implementation/brand-checks.html: Updated from spec and expectations.
* streams/reference-implementation/byte-length-queuing-strategy-expected.txt: Added.
* streams/reference-implementation/byte-length-queuing-strategy.html: Added.
* streams/reference-implementation/count-queuing-strategy-expected.txt:
* streams/reference-implementation/count-queuing-strategy.html: Updated from spec and expectations.
* streams/reference-implementation/readable-stream-cancel-expected.txt:
* streams/reference-implementation/readable-stream-cancel.html: Updated from spec and expectations and corrected
timeouts to try to overcome problems in slower bots.
* streams/reference-implementation/readable-stream-expected.txt:
* streams/reference-implementation/readable-stream-reader-expected.txt:
* streams/reference-implementation/readable-stream-reader.html: Updated from spec and expectations.
* streams/reference-implementation/readable-stream-tee-expected.txt: Added.
* streams/reference-implementation/readable-stream-tee.html: Added.
* streams/reference-implementation/readable-stream-templated-expected.txt:
* streams/reference-implementation/readable-stream-templated.html: Updated from spec and expectations.
* streams/reference-implementation/readable-stream.html: Updated from spec and expectations.
* streams/reference-implementation/resources/byte-length-queuing-strategy.js:
(ByteLengthQueuingStrategy): Updated from spec.
(typeIsObject): Deleted.
(ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure): Deleted.
* streams/reference-implementation/resources/count-queuing-strategy.js:
(CountQueuingStrategy): Updated from spec.
(typeIsObject): Deleted.
(CountQueuingStrategy.prototype.shouldApplyBackpressure): Deleted.
* streams/reference-implementation/resources/streams-utils.js:
(RandomPushSource.prototype.readStart.writeChunk):
(RandomPushSource.prototype.readStart): Renamed stream as source
(.stream.new.ReadableStream):
(sequentialReadableStream): Updated from spec.
(typeIsObject): Added.
(createDataProperty): Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@185586 268f45cc-cd09-0410-ab3c-d52691b4dbfc

33 files changed:
LayoutTests/ChangeLog
LayoutTests/resources/gc.js
LayoutTests/streams/readable-stream-controller-error-expected.txt
LayoutTests/streams/readable-stream-controller-error.html
LayoutTests/streams/readable-stream-expected.txt [deleted file]
LayoutTests/streams/readable-stream-gc.html
LayoutTests/streams/readable-stream-reader-expected.txt [deleted file]
LayoutTests/streams/readable-stream-reader-read.html
LayoutTests/streams/readable-stream-reader.html [deleted file]
LayoutTests/streams/readable-stream.html [deleted file]
LayoutTests/streams/reference-implementation/bad-strategies-expected.txt [new file with mode: 0644]
LayoutTests/streams/reference-implementation/bad-strategies.html [new file with mode: 0644]
LayoutTests/streams/reference-implementation/bad-underlying-sources-expected.txt
LayoutTests/streams/reference-implementation/bad-underlying-sources.html
LayoutTests/streams/reference-implementation/brand-checks-expected.txt
LayoutTests/streams/reference-implementation/brand-checks.html
LayoutTests/streams/reference-implementation/byte-length-queuing-strategy-expected.txt [new file with mode: 0644]
LayoutTests/streams/reference-implementation/byte-length-queuing-strategy.html [new file with mode: 0644]
LayoutTests/streams/reference-implementation/count-queuing-strategy-expected.txt
LayoutTests/streams/reference-implementation/count-queuing-strategy.html
LayoutTests/streams/reference-implementation/readable-stream-cancel-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-cancel.html
LayoutTests/streams/reference-implementation/readable-stream-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-reader-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-reader.html
LayoutTests/streams/reference-implementation/readable-stream-tee-expected.txt [new file with mode: 0644]
LayoutTests/streams/reference-implementation/readable-stream-tee.html [new file with mode: 0644]
LayoutTests/streams/reference-implementation/readable-stream-templated-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-templated.html
LayoutTests/streams/reference-implementation/readable-stream.html
LayoutTests/streams/reference-implementation/resources/byte-length-queuing-strategy.js
LayoutTests/streams/reference-implementation/resources/count-queuing-strategy.js
LayoutTests/streams/reference-implementation/resources/streams-utils.js

index d29800b..f95d0cf 100644 (file)
@@ -1,3 +1,64 @@
+2015-06-16  Youenn Fablet <youenn.fablet@crf.canon.fr> and Xabier Rodriguez Calvar  <calvaris@igalia.com>
+
+        [Streams API] Sync tests with spec
+        https://bugs.webkit.org/show_bug.cgi?id=145839
+
+        Reviewed by Darin Adler.
+
+        Considered all changes in the spec tests and brought them to WebKit.
+
+        Some of those changes were related to spec changes, like the strategy or some of the algorithms. Some like the
+        tee ones were newly added as they will be implemented soon. There are also some tests that shuffled around
+        because they were just moved at the spec or integrated from the changes we submitted, which was substantially
+        important as it allowed us to remove some of our custom tests that are now covered by the spec ones.
+
+        * resources/gc.js: Added warning about using gcRec, results might not be reliable.
+        * streams/readable-stream-controller-error-expected.txt:
+        * streams/readable-stream-controller-error.html: New failing test due to discrepancies with the reference
+        implementation. Something we will address.
+        * streams/readable-stream-expected.txt: Removed.
+        * streams/readable-stream-gc.html: Renumbered.
+        * streams/readable-stream-reader-expected.txt: Removed.
+        * streams/readable-stream-reader-read.html: Renumbered.
+        * streams/readable-stream-reader.html: Removed.
+        * streams/readable-stream.html: Removed.
+        * streams/reference-implementation/bad-strategies-expected.txt: Added.
+        * streams/reference-implementation/bad-strategies.html: Added.
+        * streams/reference-implementation/bad-underlying-sources-expected.txt:
+        * streams/reference-implementation/bad-underlying-sources.html: Updated from spec and expectations.
+        * streams/reference-implementation/brand-checks-expected.txt:
+        * streams/reference-implementation/brand-checks.html: Updated from spec and expectations.
+        * streams/reference-implementation/byte-length-queuing-strategy-expected.txt: Added.
+        * streams/reference-implementation/byte-length-queuing-strategy.html: Added.
+        * streams/reference-implementation/count-queuing-strategy-expected.txt:
+        * streams/reference-implementation/count-queuing-strategy.html: Updated from spec and expectations.
+        * streams/reference-implementation/readable-stream-cancel-expected.txt:
+        * streams/reference-implementation/readable-stream-cancel.html: Updated from spec and expectations and corrected
+        timeouts to try to overcome problems in slower bots.
+        * streams/reference-implementation/readable-stream-expected.txt:
+        * streams/reference-implementation/readable-stream-reader-expected.txt:
+        * streams/reference-implementation/readable-stream-reader.html: Updated from spec and expectations.
+        * streams/reference-implementation/readable-stream-tee-expected.txt: Added.
+        * streams/reference-implementation/readable-stream-tee.html: Added.
+        * streams/reference-implementation/readable-stream-templated-expected.txt:
+        * streams/reference-implementation/readable-stream-templated.html: Updated from spec and expectations.
+        * streams/reference-implementation/readable-stream.html: Updated from spec and expectations.
+        * streams/reference-implementation/resources/byte-length-queuing-strategy.js:
+        (ByteLengthQueuingStrategy): Updated from spec.
+        (typeIsObject): Deleted.
+        (ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure): Deleted.
+        * streams/reference-implementation/resources/count-queuing-strategy.js:
+        (CountQueuingStrategy): Updated from spec.
+        (typeIsObject): Deleted.
+        (CountQueuingStrategy.prototype.shouldApplyBackpressure): Deleted.
+        * streams/reference-implementation/resources/streams-utils.js:
+        (RandomPushSource.prototype.readStart.writeChunk):
+        (RandomPushSource.prototype.readStart): Renamed stream as source
+        (.stream.new.ReadableStream):
+        (sequentialReadableStream): Updated from spec.
+        (typeIsObject): Added.
+        (createDataProperty): Added.
+
 2015-06-16  Alexey Proskuryakov  <ap@apple.com>
 
         Fix a lint error by removing an obsolete expectation - this test doesn't fail
index 074b3c8..bd35110 100644 (file)
@@ -7,6 +7,8 @@ if (!window.gc)
     {
         if (window.GCController)
             return GCController.collect();
+
+        console.warn('Tests are running without the ability to do manual garbage collection. They will still work, but coverage will be suboptimal.');
         function gcRec(n) {
             if (n < 1)
                 return {};
index f0336e0..7351e4e 100644 (file)
@@ -1,5 +1,5 @@
 
 PASS Erroring a ReadableStream should reject ReadableStreamReader close promise 
 PASS Erroring a ReadableStream should reject ReadableStreamReader close promise 
-PASS Erroring a ReadableStream without any value 
+FAIL Erroring a ReadableStream without any value assert_equals: expected (undefined) undefined but got (object) object "Error: Error function called."
 
index 0ce7aa5..ceea3bf 100644 (file)
@@ -2,9 +2,8 @@
 <script src='../resources/testharness.js'></script>
 <script src='../resources/testharnessreport.js'></script>
 <script>
-
-t2 = async_test('Erroring a ReadableStream should reject ReadableStreamReader close promise');
-t2.step(function() {
+var test1 = async_test('Erroring a ReadableStream should reject ReadableStreamReader close promise');
+test1.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -12,19 +11,19 @@ t2.step(function() {
         }
     });
 
-   rs.getReader().closed.then(t2.step_func(function() {
+   rs.getReader().closed.then(test1.step_func(function() {
         assert_unreached("closed promise should not be resolved when stream is errored");
-    }), t2.step_func(function(err) {
+    }), test1.step_func(function(err) {
         assert_equals(rsError, err);
-        t2.done();
+        test1.done();
     }));
 
     var rsError = "my error";
     controller.error(rsError);
 });
 
-t3 = async_test('Erroring a ReadableStream should reject ReadableStreamReader close promise');
-t3.step(function() {
+var test2 = async_test('Erroring a ReadableStream should reject ReadableStreamReader close promise');
+test2.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -37,16 +36,16 @@ t3.step(function() {
 
     // Let's call getReader twice to ensure that stream is not locked to a reader.
     rs.getReader();
-    rs.getReader().closed.then(t3.step_func(function() {
+    rs.getReader().closed.then(test2.step_func(function() {
         assert_unreached("closed promise should not be resolved when stream is errored");
-    }), t3.step_func(function(err) {
+    }), test2.step_func(function(err) {
         assert_equals(rsError, err);
-        t3.done();
+        test2.done();
     }));
 });
 
-t4 = async_test('Erroring a ReadableStream without any value');
-t4.step(function() {
+var test3 = async_test('Erroring a ReadableStream without any value');
+test3.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -54,16 +53,13 @@ t4.step(function() {
         }
     });
 
-    rs.getReader().closed.then(t3.step_func(function() {
+    rs.getReader().closed.then(test3.step_func(function() {
         assert_unreached("closed promise should not be resolved when stream is errored");
-    }), t4.step_func(function(err) {
-        // Error generated by JS engine.
-        assert_true(typeof err == 'object');
-        t4.done();
+    }), test3.step_func(function(err) {
+        assert_equals(err, undefined);
+        test3.done();
     }));
 
     controller.error();
 });
-
-
 </script>
diff --git a/LayoutTests/streams/readable-stream-expected.txt b/LayoutTests/streams/readable-stream-expected.txt
deleted file mode 100644 (file)
index 7a1619e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-
-PASS ReadableStream can't be constructed with garbage 
-PASS ReadableStream start should be called with the proper parameters 
-PASS ReadableStream should be able to call start method within prototype chain of its source 
-
index e892772..d074211 100644 (file)
@@ -3,7 +3,6 @@
 <script src='../resources/testharnessreport.js'></script>
 <script src='../resources/gc.js'></script>
 <script>
-
 function garbageCollectAndDo(task)
 {
     var timeout = 50;
@@ -16,8 +15,8 @@ function garbageCollectAndDo(task)
     setTimeout(task, timeout);
 }
 
-t1 = async_test('Readable stream controller methods should continue working properly when scripts are loosing reference to the readable stream');
-t1.step(function() {
+var test1 = async_test('Readable stream controller methods should continue working properly when scripts are loosing reference to the readable stream');
+test1.step(function() {
     var controller;
     new ReadableStream({
         start: function(c) {
@@ -25,44 +24,43 @@ t1.step(function() {
         }
     });
 
-    garbageCollectAndDo(t1.step_func(function() {
+    garbageCollectAndDo(test1.step_func(function() {
         controller.close();
         assert_throws(new TypeError(), function() { controller.close(); });
         assert_throws(new TypeError(), function() { controller.error(); });
-        t1.done();
+        test1.done();
     }));
 });
 
-t2 = async_test('Readable stream closed promise should resolve even if stream and reader JS references are lost');
-t2.step(function() {
+var test2 = async_test('Readable stream closed promise should resolve even if stream and reader JS references are lost');
+test2.step(function() {
     var controller;
     new ReadableStream({
         start: function(c) {
             controller = c;
         }
-    }).getReader().closed.then(t2.step_func(function() {
-        t2.done();
+    }).getReader().closed.then(test2.step_func(function() {
+        test2.done();
     }));
 
-    garbageCollectAndDo(t2.step_func(function() {
+    garbageCollectAndDo(test2.step_func(function() {
         controller.close();
     }));
 });
 
-t3 = async_test('Readable stream closed promise should reject even if stream and reader JS references are lost');
-t3.step(function() {
+var test3 = async_test('Readable stream closed promise should reject even if stream and reader JS references are lost');
+test3.step(function() {
     var controller;
     new ReadableStream({
         start: function(c) {
             controller = c;
         }
-    }).getReader().closed.catch(t3.step_func(function() {
-        t3.done();
+    }).getReader().closed.catch(test3.step_func(function() {
+        test3.done();
     }));
 
-    garbageCollectAndDo(t3.step_func(function() {
+    garbageCollectAndDo(test3.step_func(function() {
         controller.error();
     }));
 });
-
 </script>
diff --git a/LayoutTests/streams/readable-stream-reader-expected.txt b/LayoutTests/streams/readable-stream-reader-expected.txt
deleted file mode 100644 (file)
index fc1a4a5..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-
-PASS Collecting a ReadableStreamReader should not unlock its stream 
-
index 82417b7..11f5d28 100644 (file)
@@ -2,8 +2,8 @@
 <script src='../resources/testharness.js'></script>
 <script src='../resources/testharnessreport.js'></script>
 <script>
-var t1 = async_test('Reading twice on a stream that gets closed');
-t1.step(function() {
+var test1 = async_test('Reading twice on a stream that gets closed');
+test1.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -13,18 +13,18 @@ t1.step(function() {
     var counter = 0;
     var reader = rs.getReader();
 
-    reader.read().then(t1.step_func(function(result) {
+    reader.read().then(test1.step_func(function(result) {
         assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close');
         assert_equals(counter, 1);
         counter++;
     }));
-    reader.read().then(t1.step_func(function(result) {
+    reader.read().then(test1.step_func(function(result) {
         assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close');
         assert_equals(counter, 2);
         counter++;
-        t1.done();
+        test1.done();
     }));
-    reader.closed.then(t1.step_func(function() {
+    reader.closed.then(test1.step_func(function() {
         assert_equals(counter, 0);
         counter++;
     }));
@@ -32,8 +32,8 @@ t1.step(function() {
     controller.close();
 });
 
-var t2 = async_test('Reading twice on a closed stream');
-t2.step(function() {
+var test2 = async_test('Reading twice on a closed stream');
+test2.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -46,25 +46,25 @@ t2.step(function() {
     var counter = 0;
     var reader = rs.getReader();
 
-    reader.read().then(t2.step_func(function(result) {
+    reader.read().then(test2.step_func(function(result) {
         assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close');
         assert_equals(counter, 0);
         counter++;
     }));
-    reader.read().then(t2.step_func(function(result) {
+    reader.read().then(test2.step_func(function(result) {
         assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close');
         assert_equals(counter, 1);
         counter++;
     }));
-    reader.closed.then(t2.step_func(function() {
+    reader.closed.then(test2.step_func(function() {
         assert_equals(counter, 2);
         counter++;
-        t2.done();
+        test2.done();
     }));
 });
 
-var t3 = async_test('Reading twice on an errored stream');
-t3.step(function() {
+var test3 = async_test('Reading twice on an errored stream');
+test3.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -78,32 +78,32 @@ t3.step(function() {
     var counter = 0;
     var reader = rs.getReader();
 
-    reader.read().then(t3.step_func(function() {
+    reader.read().then(test3.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t3.step_func(function(err) {
+    }), test3.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 0);
         counter++;
     }));
-    reader.read().then(t3.step_func(function() {
+    reader.read().then(test3.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t3.step_func(function(err) {
+    }), test3.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 1);
         counter++;
     }));
-    reader.closed.then(t3.step_func(function() {
+    reader.closed.then(test3.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t3.step_func(function(err) {
+    }), test3.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 2);
         counter++;
-        t3.done();
+        test3.done();
     }));
 });
 
-var t4 = async_test('Reading twice on a stream that gets errored');
-t4.step(function() {
+var test4 = async_test('Reading twice on a stream that gets errored');
+test4.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -114,24 +114,24 @@ t4.step(function() {
     var counter = 0;
     var reader = rs.getReader();
 
-    reader.read().then(t4.step_func(function() {
+    reader.read().then(test4.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t4.step_func(function(err) {
+    }), test4.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 1);
         counter++;
     }));
-    reader.read().then(t4.step_func(function() {
+    reader.read().then(test4.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t4.step_func(function(err) {
+    }), test4.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 2);
         counter++;
-        t4.done();
+        test4.done();
     }));
-    reader.closed.then(t4.step_func(function() {
+    reader.closed.then(test4.step_func(function() {
         assert_unreached('read() should reject on an errored stream');
-    }), t4.step_func(function(err) {
+    }), test4.step_func(function(err) {
         assert_equals(myError, err);
         assert_equals(counter, 0);
         counter++;
@@ -141,8 +141,8 @@ t4.step(function() {
     controller.error(myError);
  });
 
-var t5 = async_test('Reading within a read promise resolve callback on a stream that gets closed');
-t5.step(function() {
+var test5 = async_test('Reading within a read promise resolve callback on a stream that gets closed');
+test5.step(function() {
     var controller;
     var rs = new ReadableStream({
         start: function(c) {
@@ -152,9 +152,9 @@ t5.step(function() {
 
     var reader = rs.getReader();
 
-    reader.read().then(t5.step_func(function() {
-        reader.read().then(t5.step_func(function() {
-            t5.done();
+    reader.read().then(test5.step_func(function() {
+        reader.read().then(test5.step_func(function() {
+            test5.done();
         }));
     }));
     controller.close();
diff --git a/LayoutTests/streams/readable-stream-reader.html b/LayoutTests/streams/readable-stream-reader.html
deleted file mode 100644 (file)
index d08a589..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<script src='../resources/testharness.js'></script>
-<script src='../resources/testharnessreport.js'></script>
-<script src='../resources/gc.js'></script>
-<script>
-
-t1 = async_test('Collecting a ReadableStreamReader should not unlock its stream');
-t1.step(function() {
-    var rs = new ReadableStream({});
-    rs.getReader();
-    setTimeout(t1.step_func(function() {
-        window.gc();
-        assert_throws(new TypeError(), function() { rs.getReader(); }, 'old reader should still be locking a new one even after garbage collection');
-        t1.done();
-    }), 10);
-});
-
-</script>
diff --git a/LayoutTests/streams/readable-stream.html b/LayoutTests/streams/readable-stream.html
deleted file mode 100644 (file)
index 70d557c..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<!DOCTYPE html>
-<script src='../resources/testharness.js'></script>
-<script src='../resources/testharnessreport.js'></script>
-<script>
-test(function() {
-    assert_throws(new TypeError(), function() {
-        new ReadableStream(null);
-    }, 'constructor should throw when the source is null');
-}, 'ReadableStream can\'t be constructed with garbage');
-
-test(function()
-{
-    var isStartCalled = false;
-    var source = {
-        start: function(controller) {
-            assert_equals(this, source, 'source is this during start');
-
-            // FIXME: We should add constructor at some point.
-            var methods = [ 'close', 'constructor', 'enqueue', 'error' ];
-            var proto = Object.getPrototypeOf(controller);
-
-            assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), methods,
-                        'the controller should have the right methods');
-
-            for (var m of [ 'close', 'enqueue', 'error' ]) {
-                var methodProperties = [ 'length', 'name' ];
-                var propDesc = Object.getOwnPropertyDescriptor(proto, m);
-                assert_equals(propDesc.enumerable, true, 'method should be 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');
-                assert_array_equals(Object.getOwnPropertyNames(controller[m]).sort(), methodProperties, 'method should have the right properties');
-            }
-
-            assert_equals(controller.close.length, 0, 'close 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');
-
-            var propDesc = Object.getOwnPropertyDescriptor(proto, 'constructor');
-            assert_equals(propDesc.enumerable, false, 'constructor should not be enumerable');
-            assert_equals(propDesc.configurable, true, 'constructor should be configurable');
-            assert_equals(propDesc.writable, false, 'constructor should not be writable');
-            assert_equals(typeof controller.constructor, 'object', 'constructor should be an object');
-
-            isStartCalled = true;
-        }
-    };
-
-    var rs = new ReadableStream(source);
-    assert_true(isStartCalled);
-}, 'ReadableStream start should be called with the proper parameters');
-
-test(function()
-{
-    var isStartCalled = false;
-
-    var SimpleStreamSource = function() { };
-    SimpleStreamSource.prototype.start = function() { isStartCalled = true; };
-    SimpleStreamSource.prototype.constructor = SimpleStreamSource;
-
-    var rs = new ReadableStream(new SimpleStreamSource());
-    assert_true(isStartCalled);
-}, 'ReadableStream should be able to call start method within prototype chain of its source');
-
-</script>
diff --git a/LayoutTests/streams/reference-implementation/bad-strategies-expected.txt b/LayoutTests/streams/reference-implementation/bad-strategies-expected.txt
new file mode 100644 (file)
index 0000000..a9e1b8b
--- /dev/null
@@ -0,0 +1,30 @@
+
+FAIL Readable stream: throwing strategy.size getter assert_throws: construction should re-throw the error function "function () {
+        new ReadableStream({}, {
+          ..." did not throw
+FAIL Readable stream: throwing strategy.size method assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
+FAIL Readable stream: throwing strategy.highWaterMark getter assert_throws: construction should re-throw the error function "function () {
+        new ReadableStream({}, {
+          ..." did not throw
+FAIL Readable stream: invalid strategy.highWaterMark assert_throws: construction should throw a RangeError for -1 function "function () {
+            new ReadableStream({}, {
+      ..." did not throw
+FAIL Readable stream: negative strategy.highWaterMark assert_throws: construction should throw a RangeError function "function () {
+        new ReadableStream({}, {
+          ..." did not throw
+FAIL Readable stream: strategy.size returning NaN assert_equals: enqueue should throw a RangeError expected function "function RangeError() {
+    [native code]
+}" but got function "function AssertionError(message)
+    {
+        this.messa..."
+FAIL Readable stream: strategy.size returning -Infinity assert_equals: enqueue should throw a RangeError expected function "function RangeError() {
+    [native code]
+}" but got function "function AssertionError(message)
+    {
+        this.messa..."
+FAIL Readable stream: strategy.size returning +Infinity assert_equals: enqueue should throw a RangeError expected function "function RangeError() {
+    [native code]
+}" but got function "function AssertionError(message)
+    {
+        this.messa..."
+
diff --git a/LayoutTests/streams/reference-implementation/bad-strategies.html b/LayoutTests/streams/reference-implementation/bad-strategies.html
new file mode 100644 (file)
index 0000000..bfdadb5
--- /dev/null
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script>
+test(function() {
+    var theError = new Error('a unique string');
+
+    assert_throws(theError, function() {
+        new ReadableStream({}, {
+            get size() {
+                throw theError;
+            },
+            highWaterMark: 5
+        });
+    }, 'construction should re-throw the error');
+}, 'Readable stream: throwing strategy.size getter');
+
+var test1 = async_test('Readable stream: throwing strategy.size method');
+test1.step(function() {
+    var theError = new Error('a unique string');
+    var rs = new ReadableStream(
+        {
+            start: function(c) {
+                assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
+            }
+        },
+        {
+            size: function() {
+                throw theError;
+            },
+            highWaterMark: 5
+        }
+    );
+
+    rs.getReader().closed.catch(test1.step_func(function(e) {
+        assert_equals(e, theError, 'closed should reject with the error');
+        test1.done();
+    }))
+});
+
+test(function() {
+    var theError = new Error('a unique string');
+
+    assert_throws(theError, function() {
+        new ReadableStream({}, {
+            size: function() {
+                return 1;
+            },
+            get highWaterMark() {
+                throw theError;
+            }
+        });
+    }, 'construction should re-throw the error');
+}, 'Readable stream: throwing strategy.highWaterMark getter');
+
+test(function() {
+    for (var highWaterMark of [-1, -Infinity]) {
+        assert_throws(new RangeError(), function() {
+            new ReadableStream({}, {
+                size: function() {
+                    return 1;
+                },
+                highWaterMark
+            });
+        }, 'construction should throw a RangeError for ' + highWaterMark);
+    }
+
+    for (var highWaterMark of [NaN, 'foo', {}]) {
+        assert_throws(new TypeError(), function() {
+            new ReadableStream({}, {
+                size: function() {
+                    return 1;
+                },
+                highWaterMark
+            });
+        }, 'construction should throw a TypeError for ' + highWaterMark);
+    }
+}, 'Readable stream: invalid strategy.highWaterMark');
+
+test(function() {
+    assert_throws(new RangeError(), function() {
+        new ReadableStream({}, {
+            size: function() {
+                return 1;
+            },
+            highWaterMark: -1
+        });
+    }, 'construction should throw a RangeError');
+}, 'Readable stream: negative strategy.highWaterMark');
+
+var test2 = async_test('Readable stream: strategy.size returning NaN');
+test2.step(function() {
+    var theError;
+    var startCalled = false;
+    var rs = new ReadableStream(
+        {
+            start: function(c) {
+                try {
+                    c.enqueue('hi');
+                    assert_unreached('enqueue didn\'t throw');
+                } catch (error) {
+                    startCalled = true;
+                    assert_equals(error.constructor, RangeError, 'enqueue should throw a RangeError');
+                    theError = error;
+                }
+            }
+        },
+        {
+            size: function() {
+                return NaN;
+            },
+            highWaterMark: 5
+        }
+    );
+
+    rs.getReader().closed.catch(test2.step_func(function(e) {
+        assert_equals(e, theError, 'closed should reject with the error');
+        assert_true(startCalled);
+        test2.done();
+    }));
+});
+
+var test3 = async_test('Readable stream: strategy.size returning -Infinity');
+test3.step(function() {
+    var theError;
+    var startCalled = false;
+    var rs = new ReadableStream(
+        {
+            start: function(c) {
+                try {
+                    c.enqueue('hi');
+                    assert_unreached('enqueue didn\'t throw');
+                } catch (error) {
+                    startCalled = true;
+                    assert_equals(error.constructor, RangeError, 'enqueue should throw a RangeError');
+                    theError = error;
+                }
+            }
+        },
+        {
+            size: function() {
+                return -Infinity;
+            },
+            highWaterMark: 5
+        }
+    );
+
+    rs.getReader().closed.catch(test3.step_func(function(e) {
+        assert_equals(e, theError, 'closed should reject with the error');
+        assert_true(startCalled);
+        test3.done();
+    }));
+});
+
+var test4 = async_test('Readable stream: strategy.size returning +Infinity');
+test4.step(function() {
+    var theError;
+    var startCalled = false;
+    var rs = new ReadableStream(
+        {
+            start: function(c) {
+                try {
+                    c.enqueue('hi');
+                    assert_unreached('enqueue didn\'t throw');
+                } catch (error) {
+                    startCalled = true;
+                    assert_equals(error.constructor, RangeError, 'enqueue should throw a RangeError');
+                    theError = error;
+                }
+            }
+        },
+        {
+            size: function() {
+                return +Infinity;
+            },
+            highWaterMark: 5
+        }
+    );
+
+    rs.getReader().closed.catch(test4.step_func(function(e) {
+        assert_equals(e, theError, 'closed should reject with the error');
+        assert_true(startCalled);
+        test4.done();
+    }));
+});
+</script>
index 28419fa..56cf92d 100644 (file)
@@ -7,14 +7,10 @@ PASS Underlying source: throwing pull getter (second pull)
 PASS Underlying source: throwing pull method (second pull) 
 FAIL Underlying source: throwing cancel getter cancel is not implemented
 FAIL Underlying source: throwing cancel method cancel is not implemented
-FAIL Underlying source: throwing strategy getter assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
-FAIL Underlying source: throwing strategy.size getter assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
-FAIL Underlying source: throwing strategy.size method assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
-FAIL Underlying source: throwing strategy.shouldApplyBackpressure getter assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
-FAIL Underlying source: throwing strategy.shouldApplyBackpressure method assert_throws: enqueue should throw the error function "function () { c.enqueue('a'); }" did not throw
-FAIL Underlying source: strategy.size returning NaN assert_unreached: enqueue didn't throw Reached unreachable code
-FAIL Underlying source: strategy.size returning -Infinity assert_unreached: enqueue didn't throw Reached unreachable code
-FAIL Underlying source: strategy.size returning +Infinity assert_unreached: enqueue didn't throw Reached unreachable code
+FAIL Underlying source: calling enqueue on an empty canceled stream should not throw cancel is not implemented
+FAIL Underlying source: calling enqueue on a non-empty canceled stream should not throw cancel is not implemented
+PASS Underlying source: calling enqueue on a closed stream should throw 
+PASS Underlying source: calling enqueue on an errored stream should throw 
 PASS Underlying source: calling close twice on an empty stream should throw the second time 
 PASS Underlying source: calling close twice on a non-empty stream should throw the second time 
 FAIL Underlying source: calling close on an empty canceled stream should not throw cancel is not implemented
@@ -22,4 +18,6 @@ FAIL Underlying source: calling close on a non-empty canceled stream should not
 PASS Underlying source: calling close after error should throw 
 PASS Underlying source: calling error twice should throw the second time 
 PASS Underlying source: calling error after close should throw 
+PASS Underlying source: calling error and returning a rejected promise from start should cause the stream to error with the first error 
+PASS Underlying source: calling error and returning a rejected promise from pull should cause the stream to error with the first error 
 
index 23ffd89..35def38 100644 (file)
@@ -149,231 +149,80 @@ test6.step(function() {
         }));
 });
 
-var test7 = async_test('Underlying source: throwing strategy getter', { timeout: 50 });
+var test7 = async_test('Underlying source: calling enqueue on an empty canceled stream should not throw');
 test7.step(function() {
-    var started = false;
-    var theError = new Error('a unique string');
-
+    var controller;
     var rs = new ReadableStream({
         start: function(c) {
-            started = true;
-            assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
-        },
-        get strategy() {
-            throw theError;
+            controller = c;
         }
     });
 
-    rs.getReader().closed.catch(test7.step_func(function(e) {
-        assert_true(started);
-        assert_equals(e, theError, 'closed should reject with the error');
-        test7.done();
+    rs.cancel();
+    controller.enqueue('a') // Calling enqueue after canceling should not throw anything.
+
+    rs.getReader().closed.then(test7.step_func(function() {
+        test7.done('closed should fulfill');
     }));
 });
 
-var test8 = async_test('Underlying source: throwing strategy.size getter', { timeout: 50 });
+var test8 = async_test('Underlying source: calling enqueue on a non-empty canceled stream should not throw');
 test8.step(function() {
-    var started = false;
-    var theError = new Error('a unique string');
+    var controller;
     var rs = new ReadableStream({
         start: function(c) {
-            started = true;
-            assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
-        },
-        strategy: {
-            get size() {
-                throw theError;
-            },
-            shouldApplyBackpressure: function() {
-                return true;
-            }
+            c.enqueue('a');
+            c.enqueue('b');
+            controller = c;
         }
     });
 
-    rs.getReader().closed.catch(test8.step_func(function(e) {
-        assert_true(started);
-        assert_equals(e, theError, 'closed should reject with the error');
-        test8.done();
+    rs.cancel();
+    controller.enqueue('c') // Calling enqueue after canceling should not throw anything.
+
+    rs.getReader().closed.then(test8.step_func(function() {
+        test8.done('closed should fulfill');
     }));
 });
 
-var test9 = async_test('Underlying source: throwing strategy.size method', { timeout: 50 });
+var test9 = async_test('Underlying source: calling enqueue on a closed stream should throw');
 test9.step(function() {
-    var started = false;
-    var theError = new Error('a unique string');
-    var rs = new ReadableStream({
+    new ReadableStream({
         start: function(c) {
-            started = true;
-            assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
-        },
-        strategy: {
-            size: function() {
-                throw theError;
-            },
-            shouldApplyBackpressure: function() {
-                return true;
-            }
+            c.close();
+            assert_throws(new TypeError(), function() { c.enqueue('a') }, 'call to enqueue should throw a TypeError');
         }
-    });
-
-    rs.getReader().closed.catch(test9.step_func(function(e) {
-        assert_true(started);
-        assert_equals(e, theError, 'closed should reject with the error');
-        test9.done();
+    }).getReader().closed.then(test9.step_func(function() {
+        test9.done('closed should fulfill');
     }));
 });
 
-var test10 = async_test('Underlying source: throwing strategy.shouldApplyBackpressure getter', { timeout: 50 });
+var test10 = async_test('Underlying source: calling enqueue on an errored stream should throw');
 test10.step(function() {
-    var started = false;
-    var theError = new Error('a unique string');
-    var rs = new ReadableStream({
+    var theError = new Error('boo');
+    new ReadableStream({
         start: function(c) {
-            started = true;
-            assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
-        },
-        strategy: {
-            size: function() {
-                return 1;
-            },
-            get shouldApplyBackpressure() {
-                throw theError;
-            }
+            c.error(theError);
+            assert_throws(theError, function() { c.enqueue('a') }, 'call to enqueue should throw the error');
         }
-    });
-
-    rs.getReader().closed.catch(test10.step_func(function(e) {
-        assert_true(started);
+    }).getReader().closed.catch(test10.step_func(function(e) {
         assert_equals(e, theError, 'closed should reject with the error');
         test10.done();
     }));
 });
 
-var test11 = async_test('Underlying source: throwing strategy.shouldApplyBackpressure method', { timeout: 50 });
+var test11 = async_test('Underlying source: calling close twice on an empty stream should throw the second time');
 test11.step(function() {
-    var started = false;
-    var theError = new Error('a unique string');
-    var rs = new ReadableStream({
-        start: function(c) {
-            started = true;
-            assert_throws(theError, function() { c.enqueue('a'); }, 'enqueue should throw the error');
-        },
-        strategy: {
-            size: function() {
-                return 1;
-            },
-            shouldApplyBackpressure: function() {
-                throw theError;
-            }
-        }
-    });
-
-    rs.getReader().closed.catch(test11.step_func(function(e) {
-        assert_true(started);
-        assert_equals(e, theError, 'closed should reject with the error');
-        test11.done();
-    }));
-});
-
-var test12 = async_test('Underlying source: strategy.size returning NaN', { timeout: 50 });
-test12.step(function() {
-    var theError = undefined;
-    var rs = new ReadableStream({
-        start: function(c) {
-            try {
-                c.enqueue('hi');
-                assert_unreached('enqueue didn\'t throw');
-            } catch (error) {
-                theError = error;
-                assert_throws(new RangeError(), function() { throw error; }, 'enqueue should throw a RangeError');
-            }
-        },
-        strategy: {
-            size: function() {
-                return NaN;
-            },
-            shouldApplyBackpressure: function() {
-                return true;
-            }
-        }
-    });
-
-    rs.getReader().closed.catch(test12.step_func(function(e) {
-        assert_equals(e, theError, 'closed should reject with the error');
-        test12.done();
-    }));
-});
-
-var test13 = async_test('Underlying source: strategy.size returning -Infinity', { timeout: 50 });
-test13.step(function() {
-    var theError = undefined;
-    var rs = new ReadableStream({
-        start: function(c) {
-            try {
-                c.enqueue('hi');
-                assert_unreached('enqueue didn\'t throw');
-            } catch (error) {
-                theError = error;
-                assert_throws(new RangeError(), function() { throw error; }, 'enqueue should throw a RangeError');
-            }
-        },
-        strategy: {
-            size: function() {
-                return -Infinity;
-            },
-            shouldApplyBackpressure: function() {
-                return true;
-            }
-        }
-    });
-
-    rs.getReader().closed.catch(test13.step_func(function(e) {
-        assert_equals(e, theError, 'closed should reject with the error');
-        test13.done();
-    }));
-});
-
-var test14 = async_test('Underlying source: strategy.size returning +Infinity', { timeout: 50 });
-test14.step(function() {
-    var theError = undefined;
-    var rs = new ReadableStream({
-        start: function(c) {
-            try {
-                c.enqueue('hi');
-                assert_unreached('enqueue didn\'t throw');
-            } catch (error) {
-                theError = error;
-                assert_throws(new RangeError(), function() { throw error; }, 'enqueue should throw a RangeError');
-            }
-        },
-        strategy: {
-            size: function() {
-                return +Infinity;
-            },
-            shouldApplyBackpressure: function() {
-                return true;
-            }
-        }
-    });
-
-    rs.getReader().closed.catch(test14.step_func(function(e) {
-        assert_equals(e, theError, 'closed should reject with the error');
-        test14.done();
-    }));
-});
-
-var test15 = async_test('Underlying source: calling close twice on an empty stream should throw the second time');
-test15.step(function() {
     new ReadableStream({
         start: function(c) {
             c.close();
             assert_throws(new TypeError(), c.close, 'second call to close should throw a TypeError');
         }
-    }).getReader().closed.then(test15.step_func(function() { test15.done('closed should fulfill'); }));
+    }).getReader().closed.then(test11.step_func(function() { test11.done('closed should fulfill'); }));
 });
 
-var test16 = async_test('Underlying source: calling close twice on a non-empty stream should throw the second time');
-test16.step(function() {
+var test12 = async_test('Underlying source: calling close twice on a non-empty stream should throw the second time');
+test12.step(function() {
     var startCalled = false;
     var readCalled = false;
     var reader = new ReadableStream({
@@ -385,19 +234,19 @@ test16.step(function() {
         }
     }).getReader();
 
-    reader.read().then(test16.step_func(function(r) {
+    reader.read().then(test12.step_func(function(r) {
         assert_object_equals(r, { value: 'a', done: false }, 'read() should read the enqueued chunk');
         readCalled = true;
     }));
-    reader.closed.then(test16.step_func(function() {
+    reader.closed.then(test12.step_func(function() {
         assert_true(startCalled);
         assert_true(readCalled);
-        test16.done('closed should fulfill');
+        test12.done('closed should fulfill');
     }));
 });
 
-var test17 = async_test('Underlying source: calling close on an empty canceled stream should not throw');
-test17.step(function() {
+var test13 = async_test('Underlying source: calling close on an empty canceled stream should not throw');
+test13.step(function() {
     var controller;
     var startCalled = false;
     var rs = new ReadableStream({
@@ -410,14 +259,14 @@ test17.step(function() {
     rs.cancel();
     controller.close(); // Calling close after canceling should not throw anything.
 
-    rs.getReader().closed.then(test17.step_func(function() {
+    rs.getReader().closed.then(test13.step_func(function() {
         assert_true(startCalled);
-        test17.done('closed should fulfill');
+        test13.done('closed should fulfill');
     }));
 });
 
-var test18 = async_test('Underlying source: calling close on a non-empty canceled stream should not throw');
-test18.step(function() {
+var test14 = async_test('Underlying source: calling close on a non-empty canceled stream should not throw');
+test14.step(function() {
     var controller;
     var startCalled = false;
     var rs = new ReadableStream({
@@ -431,14 +280,14 @@ test18.step(function() {
     rs.cancel();
     controller.close(); // Calling close after canceling should not throw anything.
 
-    rs.getReader().closed.then(test18.step_func(function() {
+    rs.getReader().closed.then(test14.step_func(function() {
         assert_true(startCalled);
-        test18.done('closed should fulfill');
+        test14.done('closed should fulfill');
     }));
 });
 
-var test19 = async_test('Underlying source: calling close after error should throw');
-test19.step(function() {
+var test15 = async_test('Underlying source: calling close after error should throw');
+test15.step(function() {
     var theError = new Error('boo');
     var startCalled = false;
     new ReadableStream({
@@ -447,15 +296,15 @@ test19.step(function() {
             assert_throws(new TypeError(), c.close, 'call to close should throw a TypeError');
             startCalled = true;
         }
-    }).getReader().closed.catch(test19.step_func(function(e) {
+    }).getReader().closed.catch(test15.step_func(function(e) {
         assert_true(startCalled);
         assert_equals(e, theError, 'closed should reject with the error')
-        test19.done();
+        test15.done();
     }));
 });
 
-var test20 = async_test('Underlying source: calling error twice should throw the second time');
-test20.step(function() {
+var test16 = async_test('Underlying source: calling error twice should throw the second time');
+test16.step(function() {
     var theError = new Error('boo');
     var startCalled = false;
     new ReadableStream({
@@ -464,15 +313,15 @@ test20.step(function() {
             assert_throws(new TypeError(), c.error, 'second call to error should throw a TypeError');
             startCalled = true;
         }
-    }).getReader().closed.catch(test20.step_func(function(e) {
+    }).getReader().closed.catch(test16.step_func(function(e) {
         assert_true(startCalled);
         assert_equals(e, theError, 'closed should reject with the error');
-        test20.done();
+        test16.done();
     }));
 });
 
-var test21 = async_test('Underlying source: calling error after close should throw');
-test21.step(function() {
+var test17 = async_test('Underlying source: calling error after close should throw');
+test17.step(function() {
     var startCalled = false;
     new ReadableStream({
         start: function(c) {
@@ -480,9 +329,49 @@ test21.step(function() {
             assert_throws(new TypeError(), c.error, 'call to error should throw a TypeError');
             startCalled = true;
         }
-    }).getReader().closed.then(test21.step_func(function() {
+    }).getReader().closed.then(test17.step_func(function() {
         assert_true(startCalled);
-        test21.done('closed should fulfill');
+        test17.done('closed should fulfill');
+    }));
+});
+
+var test18 = async_test('Underlying source: calling error and returning a rejected promise from start should cause the stream to error with the first error');
+test18.step(function() {
+    var startCalled = false;
+    var firstError = new Error('1');
+    var secondError = new Error('2');
+    new ReadableStream({
+        start: function(c) {
+            c.error(firstError);
+
+            startCalled = true;
+
+            return Promise.reject(secondError);
+        }
+    }).getReader().closed.catch(test18.step_func(function(e) {
+        assert_true(startCalled);
+        assert_equals(e, firstError, 'stream should error with the first error');
+        test18.done();
+    }));
+});
+
+var test19 = async_test('Underlying source: calling error and returning a rejected promise from pull should cause the stream to error with the first error');
+test19.step(function() {
+    var startCalled = false;
+    var firstError = new Error('1');
+    var secondError = new Error('2');
+    new ReadableStream({
+        pull: function(c) {
+            c.error(firstError);
+
+            startCalled = true;
+
+            return Promise.reject(secondError);
+        }
+    }).getReader().closed.catch(test19.step_func(function(e) {
+        assert_true(startCalled);
+        assert_equals(e, firstError, 'stream should error with the first error');
+        test19.done();
     }));
 });
 </script>
index e5e7f3f..ef49f4b 100644 (file)
@@ -1,18 +1,18 @@
 
 PASS Can get the ReadableStreamReader constructor indirectly 
 PASS Can get the ReadableStreamController constructor indirectly 
+FAIL ReadableStream.prototype.pipeThrough works generically on its this and its arguments Can only call ReadableStream.pipeThrough on instances of ReadableStream
 PASS ReadableStreamReader enforces a brand check on its argument 
 FAIL ReadableStreamReader.prototype.closed enforces a brand check undefined is not an object (evaluating 'Object.getOwnPropertyDescriptor(obj, getterName).get')
 FAIL ReadableStreamReader.prototype.cancel enforces a brand check Can only call ReadableStreamReader.cancel on instances of ReadableStreamReader
 FAIL ReadableStreamReader.prototype.read enforces a brand check Can only call ReadableStreamReader.read on instances of ReadableStreamReader
+FAIL ReadableStreamReader.prototype.read enforces a brand check Can only call ReadableStreamReader.read on instances of ReadableStreamReader
 PASS ReadableStreamReader.prototype.releaseLock enforces a brand check 
 PASS ReadableStreamController enforces a brand check on its argument 
 PASS ReadableStreamController can't be given a fully-constructed ReadableStream 
 PASS ReadableStreamController.prototype.close enforces a brand check 
 PASS ReadableStreamController.prototype.enqueue enforces a brand check 
 PASS ReadableStreamController.prototype.error enforces a brand check 
-PASS ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure enforces a brand check 
 PASS ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments 
-PASS CountQueuingStrategy.prototype.shouldApplyBackpressure enforces a brand check 
 PASS CountQueuingStrategy.prototype.size should work generically on its this and its arguments 
 
index 9cbf11d..197b8a6 100644 (file)
@@ -25,9 +25,10 @@ test(function() {
 function fakeReadableStream() {
     return {
         cancel: function(reason) { return Promise.resolve(); },
+        getReader: function() { return new ReadableStream(new ReadableStream()); },
         pipeThrough: function(obj, options) { return obj.readable; },
         pipeTo: function() { return Promise.resolve(); },
-        getReader: function() { return new ReadableStream(new ReadableStream()); }
+        tee: function() { return [realReadableStream(), realReadableStream()]; }
     };
 }
 
@@ -54,9 +55,7 @@ function fakeReadableStreamController() {
 
 function fakeByteLengthQueuingStrategy() {
     return {
-        shouldApplyBackpressure: function(queueSize) {
-            return queueSize > 1;
-        },
+        highWaterMark: 0,
         size: function(chunk) {
             return chunk.byteLength;
         }
@@ -64,22 +63,20 @@ function fakeByteLengthQueuingStrategy() {
 }
 
 function realByteLengthQueuingStrategy() {
-    return new ByteLengthQueuingStrategy({ "highWaterMark": 1});
+    return new ByteLengthQueuingStrategy({ highWaterMark: 1 });
 }
 
 function fakeCountQueuingStrategy() {
     return {
-        shouldApplyBackpressure: function(queueSize) {
-            return queueSize > 1;
-        },
+        highWaterMark: 0,
         size: function(chunk) {
             return 1;
         }
-    };
+    }
 }
 
 function realCountQueuingStrategy() {
-    return new CountQueuingStrategy({ "highWaterMark": 1});
+    return new CountQueuingStrategy({ highWaterMark: 1 });
 }
 
 function getterRejects(test, obj, getterName, target, endTest) {
@@ -115,6 +112,22 @@ function methodThrows(obj, methodName, target) {
 }
 
 test(function() {
+    var pipeToArguments;
+    var thisValue = {
+        pipeTo: function() {
+            pipeToArguments = arguments;
+        }
+    };
+
+    var input = { readable: {}, writable: {} };
+    var options = {};
+    var result = ReadableStream.prototype.pipeThrough.call(thisValue, input, options);
+
+    assert_array_equals(pipeToArguments, [input.writable, options], 'correct arguments should be passed to thisValue.pipeTo');
+    assert_equals(result, input.readable, 'return value should be the passed readable property');
+}, 'ReadableStream.prototype.pipeThrough works generically on its this and its arguments');
+
+test(function() {
     assert_throws(new TypeError(), function() { new ReadableStreamReader(fakeReadableStream()); }, 'Constructing a ReadableStreamReader should throw');
 }, 'ReadableStreamReader enforces a brand check on its argument');
 
@@ -136,6 +149,12 @@ test3.step(function() {
     methodRejects(test3, ReadableStreamReader.prototype, 'read', realReadableStream(), true);
 });
 
+var test4 = async_test('ReadableStreamReader.prototype.read enforces a brand check');
+test4.step(function() {
+    methodRejects(test4, ReadableStreamReader.prototype, 'read', fakeReadableStreamReader());
+    methodRejects(test4, ReadableStreamReader.prototype, 'read', realReadableStream(), true);
+});
+
 test(function() {
     methodThrows(ReadableStreamReader.prototype, 'releaseLock', fakeReadableStreamReader());
     methodThrows(ReadableStreamReader.prototype, 'releaseLock', realReadableStream());
@@ -162,11 +181,6 @@ test(function() {
 }, 'ReadableStreamController.prototype.error enforces a brand check');
 
 test(function() {
-    methodThrows(ByteLengthQueuingStrategy.prototype, 'shouldApplyBackpressure', fakeByteLengthQueuingStrategy());
-    methodThrows(ByteLengthQueuingStrategy.prototype, 'shouldApplyBackpressure', realCountQueuingStrategy());
-}, 'ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure enforces a brand check');
-
-test(function() {
     var thisValue = null;
     var returnValue = { 'returned from': 'byteLength getter' };
     var chunk = {
@@ -179,11 +193,6 @@ test(function() {
 }, 'ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments');
 
 test(function() {
-    methodThrows(CountQueuingStrategy.prototype, 'shouldApplyBackpressure', fakeCountQueuingStrategy());
-    methodThrows(CountQueuingStrategy.prototype, 'shouldApplyBackpressure', realByteLengthQueuingStrategy());
-}, 'CountQueuingStrategy.prototype.shouldApplyBackpressure enforces a brand check');
-
-test(function() {
     var thisValue = null;
     var chunk = {
         get byteLength() {
diff --git a/LayoutTests/streams/reference-implementation/byte-length-queuing-strategy-expected.txt b/LayoutTests/streams/reference-implementation/byte-length-queuing-strategy-expected.txt
new file mode 100644 (file)
index 0000000..5da2df5
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS Can construct a ByteLengthQueuingStrategy with a valid high water mark 
+PASS Can construct a ByteLengthQueuingStrategy with any value as its high water mark 
+PASS ByteLengthQueuingStrategy instances have the correct properties 
+
diff --git a/LayoutTests/streams/reference-implementation/byte-length-queuing-strategy.html b/LayoutTests/streams/reference-implementation/byte-length-queuing-strategy.html
new file mode 100644 (file)
index 0000000..e30591f
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='resources/streams-utils.js'></script>
+<script src='resources/byte-length-queuing-strategy.js'></script>
+<script>
+test(function() {
+    var strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
+}, 'Can construct a ByteLengthQueuingStrategy with a valid high water mark');
+
+test(function() {
+    for (var highWaterMark of [-Infinity, NaN, 'foo', {}, function () {}]) {
+        var strategy = new ByteLengthQueuingStrategy({ highWaterMark });
+        assert_true(Object.is(strategy.highWaterMark, highWaterMark), highWaterMark + ' gets set correctly');
+    }
+}, 'Can construct a ByteLengthQueuingStrategy with any value as its high water mark');
+
+test(function() {
+    var 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 connstructor');
+    assert_equals(typeof strategy.size, 'function');
+}, 'ByteLengthQueuingStrategy instances have the correct properties');
+</script>
index b9bb735..2faef6e 100644 (file)
@@ -1,8 +1,9 @@
 
 PASS Can construct a CountQueuingStrategy with a valid high water mark 
-PASS Gives a RangeError when the number is negative 
+PASS Can construct a CountQueuingStrategy with any value as its high water mark 
+PASS CountQueuingStrategy instances have the correct properties 
 PASS Can construct a readable stream with a valid CountQueuingStrategy 
-FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 0) assert_equals: After 0 reads, 1st enqueue should return false (queue now contains 1 chunk) expected (boolean) false but got (undefined) undefined
-FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 1) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected (boolean) true but got (undefined) undefined
-FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 4) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected (boolean) true but got (undefined) undefined
+FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 0) assert_equals: 0 reads, 0 enqueues: desiredSize should be 0 expected (number) 0 but got (undefined) undefined
+FAIL Correctly governs a ReadableStreamController's desiredSize property (HWM = 1) assert_equals: 0 reads, 0 enqueues: desiredSize should be 1 expected (number) 1 but got (undefined) undefined
+FAIL Correctly governs a ReadableStreamController's desiredSize property (HWM = 4) assert_equals: 0 reads, 0 enqueues: desiredSize should be 4 expected (number) 4 but got (undefined) undefined
 
index 3b9645b..555ca51 100644 (file)
@@ -5,31 +5,50 @@
 <script src='resources/count-queuing-strategy.js'></script>
 <script>
 test(function() {
-    new CountQueuingStrategy({ "highWaterMark": 4});
+    new CountQueuingStrategy({ highWaterMark: 4 });
 }, 'Can construct a CountQueuingStrategy with a valid high water mark');
 
 test(function() {
-    assert_throws(new RangeError(), function() { new CountQueuingStrategy({ "highWaterMark": -4 }) }, 'throws for { highWaterMark: -4 }');
-    assert_throws(new RangeError(), function() { new CountQueuingStrategy({ "highWaterMark": '-4' }) }, 'throws for { highWaterMark: \'-4\' }');
-}, 'Gives a RangeError when the number is negative');
+    for (var highWaterMark of [-Infinity, NaN, 'foo', {}, function () {}]) {
+        var strategy = new CountQueuingStrategy({ highWaterMark });
+        assert_true(Object.is(strategy.highWaterMark, highWaterMark), highWaterMark + ' gets set correctly');
+    }
+}, 'Can construct a CountQueuingStrategy with any value as its high water mark');
 
 test(function() {
-    new ReadableStream({ strategy: new CountQueuingStrategy({ "highWaterMark": 4 }) });
+    var 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 connstructor');
+    assert_equals(typeof strategy.size, 'function');
+}, 'CountQueuingStrategy instances have the correct properties');
+
+test(function() {
+    new ReadableStream({}, new CountQueuingStrategy({ highWaterMark: 4 }));
 }, 'Can construct a readable stream with a valid CountQueuingStrategy');
 
 var test1 = async_test('Correctly governs the return value of a ReadableStream\'s enqueue function (HWM = 0)');
 test1.step(function() {
-    var enqueue;
+    var controller;
     var rs = new ReadableStream({
-        start: function(c) { enqueue = c.enqueue.bind(c); },
-        strategy: new CountQueuingStrategy({ "highWaterMark": 0 })
-    });
+        start: function(c) {
+            controller = c;
+        }
+    },
+    new CountQueuingStrategy({ highWaterMark: 0 })
+    );
     var reader = rs.getReader();
 
-    assert_equals(enqueue('a'), false, 'After 0 reads, 1st enqueue should return false (queue now contains 1 chunk)');
-    assert_equals(enqueue('b'), false, 'After 0 reads, 2nd enqueue should return false (queue now contains 2 chunks)');
-    assert_equals(enqueue('c'), false, 'After 0 reads, 3rd enqueue should return false (queue now contains 3 chunks)');
-    assert_equals(enqueue('d'), false, 'After 0 reads, 4th enqueue should return false (queue now contains 4 chunks)');
+    assert_equals(controller.desiredSize, 0, '0 reads, 0 enqueues: desiredSize should be 0');
+    controller.enqueue('a');
+    assert_equals(controller.desiredSize, -1, '0 reads, 1 enqueue: desiredSize should be -1');
+    controller.enqueue('b');
+    assert_equals(controller.desiredSize, -2, '0 reads, 2 enqueues: desiredSize should be -2');
+    controller.enqueue('c');
+    assert_equals(controller.desiredSize, -3, '0 reads, 3 enqueues: desiredSize should be -3');
+    controller.enqueue('d');
+    assert_equals(controller.desiredSize, -4, '0 reads, 4 enqueues: desiredSize should be -4');
 
     reader.read().then(test1.step_func(function(result) {
         assert_object_equals(result, { value: 'a', done: false }, '1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
@@ -39,32 +58,49 @@ test1.step(function() {
         return reader.read();
     })).then(test1.step_func(function(result) {
         assert_object_equals(result, { value: 'c', done: false }, '3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
-        assert_equals(enqueue('e'), false, 'After 3 reads, 5th enqueue should return false (queue now contains 2 chunks)');
+
+        assert_equals(controller.desiredSize, -1, '3 reads, 4 enqueues: desiredSize should be -1');
+        controller.enqueue('e');
+        assert_equals(controller.desiredSize, -2, '3 reads, 5 enqueues: desiredSize should be -2');
+
         return reader.read();
     })).then(test1.step_func(function(result) {
         assert_object_equals(result, { value: 'd', done: false }, '4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
         return reader.read();
     })).then(test1.step_func(function(result) {
         assert_object_equals(result, { value: 'e', done: false }, '5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
-        assert_equals(enqueue('f'), false, 'After 5 reads, 6th enqueue should return false (queue now contains 1 chunk)');
-        assert_equals(enqueue('g'), false, 'After 5 reads, 7th enqueue should return false (queue now contains 2 chunks)');
+
+        assert_equals(controller.desiredSize, 0, '5 reads, 5 enqueues: desiredSize should be 0');
+        controller.enqueue('f');
+        assert_equals(controller.desiredSize, -1, '5 reads, 6 enqueues: desiredSize should be -1');
+        controller.enqueue('g');
+        assert_equals(controller.desiredSize, -2, '5 reads, 7 enqueues: desiredSize should be -2');
+
         test1.done();
     })).catch(test1.step_func(function(e) { assert_unreached(e); } ));
 });
 
-var test2 = async_test('Correctly governs the return value of a ReadableStream\'s enqueue function (HWM = 1)');
+var test2 = async_test('Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 1)');
 test2.step(function() {
-    var enqueue;
+    var controller;
     var rs = new ReadableStream({
-        start: function(c) { enqueue = c.enqueue.bind(c); },
-        strategy: new CountQueuingStrategy({ "highWaterMark": 1 })
-    });
+        start: function(c) {
+            controller = c;
+        },
+    },
+    new CountQueuingStrategy({ highWaterMark: 1 })
+    );
     var reader = rs.getReader();
 
-    assert_equals(enqueue('a'), true, 'After 0 reads, 1st enqueue should return true (queue now contains 1 chunk)');
-    assert_equals(enqueue('b'), false, 'After 0 reads, 2nd enqueue should return false (queue now contains 2 chunks)');
-    assert_equals(enqueue('c'), false, 'After 0 reads, 3rd enqueue should return false (queue now contains 3 chunks)');
-    assert_equals(enqueue('d'), false, 'After 0 reads, 4th enqueue should return false (queue now contains 4 chunks)');
+    assert_equals(controller.desiredSize, 1, '0 reads, 0 enqueues: desiredSize should be 1');
+    controller.enqueue('a');
+    assert_equals(controller.desiredSize, 0, '0 reads, 1 enqueue: desiredSize should be 0');
+    controller.enqueue('b');
+    assert_equals(controller.desiredSize, -1, '0 reads, 2 enqueues: desiredSize should be -1');
+    controller.enqueue('c');
+    assert_equals(controller.desiredSize, -2, '0 reads, 3 enqueues: desiredSize should be -2');
+    controller.enqueue('d');
+    assert_equals(controller.desiredSize, -3, '0 reads, 4 enqueues: desiredSize should be -3');
 
     reader.read().then(test2.step_func(function(result) {
         assert_object_equals(result, { value: 'a', done: false }, '1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
@@ -74,41 +110,65 @@ test2.step(function() {
         return reader.read();
     })).then(test2.step_func(function(result) {
         assert_object_equals(result, { value: 'c', done: false }, '3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
-        assert_equals(enqueue('e'), false, 'After 3 reads, 5th enqueue should return false (queue now contains 2 chunks)');
+
+        assert_equals(controller.desiredSize, 0, '3 reads, 4 enqueues: desiredSize should be 0');
+        controller.enqueue('e');
+        assert_equals(controller.desiredSize, -1, '3 reads, 5 enqueues: desiredSize should be -1');
+
         return reader.read();
     })).then(test2.step_func(function(result) {
         assert_object_equals(result, { value: 'd', done: false }, '4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
         return reader.read();
     })).then(test2.step_func(function(result) {
         assert_object_equals(result, { value: 'e', done: false }, '5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
-        assert_equals(enqueue('f'), true, 'After 5 reads, 6th enqueue should return true (queue now contains 1 chunk)');
-        assert_equals(enqueue('g'), false, 'After 5 reads, 7th enqueue should return false (queue now contains 2 chunks)');
+
+        assert_equals(controller.desiredSize, 1, '5 reads, 5 enqueues: desiredSize should be 1');
+        controller.enqueue('f');
+        assert_equals(controller.desiredSize, 0, '5 reads, 6 enqueues: desiredSize should be 0');
+        controller.enqueue('g');
+        assert_equals(controller.desiredSize, -1, '5 reads, 7 enqueues: desiredSize should be -1');
+
         test2.done();
     })).catch(test2.step_func(function(e) { assert_unreached(e); } ));
 });
 
-var test3 = async_test('Correctly governs the return value of a ReadableStream\'s enqueue function (HWM = 4)');
+var test3 = async_test('Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 4)');
 test3.step(function() {
-    var enqueue;
+    var controller;
     var rs = new ReadableStream({
-        start: function(c) { enqueue = c.enqueue.bind(c); },
-        strategy: new CountQueuingStrategy({ "highWaterMark": 4 })
-    });
+        start: function(c) {
+            controller = c;
+        }
+    },
+    new CountQueuingStrategy({ highWaterMark: 4 })
+    );
     var reader = rs.getReader();
 
-    assert_equals(enqueue('a'), true, 'After 0 reads, 1st enqueue should return true (queue now contains 1 chunk)');
-    assert_equals(enqueue('b'), true, 'After 0 reads, 2nd enqueue should return true (queue now contains 2 chunks)');
-    assert_equals(enqueue('c'), true, 'After 0 reads, 3rd enqueue should return true (queue now contains 3 chunks)');
-    assert_equals(enqueue('d'), true, 'After 0 reads, 4th enqueue should return true (queue now contains 4 chunks)');
-    assert_equals(enqueue('e'), false, 'After 0 reads, 5th enqueue should return false (queue now contains 5 chunks)');
-    assert_equals(enqueue('f'), false, 'After 0 reads, 6th enqueue should return false (queue now contains 6 chunks)');
+    assert_equals(controller.desiredSize, 4, '0 reads, 0 enqueues: desiredSize should be 4');
+    controller.enqueue('a');
+    assert_equals(controller.desiredSize, 3, '0 reads, 1 enqueue: desiredSize should be 3');
+    controller.enqueue('b');
+    assert_equals(controller.desiredSize, 2, '0 reads, 2 enqueues: desiredSize should be 2');
+    controller.enqueue('c');
+    assert_equals(controller.desiredSize, 1, '0 reads, 3 enqueues: desiredSize should be 1');
+    controller.enqueue('d');
+    assert_equals(controller.desiredSize, 0, '0 reads, 4 enqueues: desiredSize should be 0');
+    controller.enqueue('e');
+    assert_equals(controller.desiredSize, -1, '0 reads, 5 enqueues: desiredSize should be -1');
+    controller.enqueue('f');
+    assert_equals(controller.desiredSize, -2, '0 reads, 6 enqueues: desiredSize should be -2');
+
 
     reader.read().then(test3.step_func(function(result) {
         assert_object_equals(result, { value: 'a', done: false }, '1st read gives back the 1st chunk enqueued (queue now contains 5 chunks)');
         return reader.read();
     })).then(test3.step_func(function(result) {
         assert_object_equals(result, { value: 'b', done: false }, '2nd read gives back the 2nd chunk enqueued (queue now contains 4 chunks)');
-        assert_equals(enqueue('g'), false, 'After 2 reads, 7th enqueue should return false (queue now contains 5 chunks)');
+
+        assert_equals(controller.desiredSize, 0, '2 reads, 6 enqueues: desiredSize should be 0');
+        controller.enqueue('g');
+        assert_equals(controller.desiredSize, -1, '2 reads, 7 enqueues: desiredSize should be -1');
+
         return reader.read();
     })).then(test3.step_func(function(result) {
         assert_object_equals(result, { value: 'c', done: false }, '3rd read gives back the 3rd chunk enqueued (queue now contains 4 chunks)');
@@ -120,12 +180,19 @@ test3.step(function() {
         assert_object_equals(result, { value: 'e', done: false }, '5th read gives back the 5th chunk enqueued (queue now contains 2 chunks)');
         return reader.read();
     })).then(test3.step_func(function(result) {
-        assert_object_equals(result, { value: 'f', done: false }, '6th read gives back the 6th chunk enqueued (queue now contains 1 chunk)');
-        assert_equals(enqueue('h'), true, 'After 6 reads, 8th enqueue should return true (queue now contains 2 chunks)');
-        assert_equals(enqueue('i'), true, 'After 6 reads, 9th enqueue should return true (queue now contains 3 chunks)');
-        assert_equals(enqueue('j'), true, 'After 6 reads, 10th enqueue should return true (queue now contains 4 chunks)');
-        assert_equals(enqueue('k'), false, 'After 6 reads, 11th enqueue should return false (queue now contains 5 chunks)');
+        assert_object_equals(result, { value: 'f', done: false }, '6th read gives back the 6th chunk enqueued (queue now contains 0 chunks)');
+
+        assert_equals(controller.desiredSize, 3, '6 reads, 7 enqueues: desiredSize should be 3');
+        controller.enqueue('h');
+        assert_equals(controller.desiredSize, 2, '6 reads, 8 enqueues: desiredSize should be 2');
+        controller.enqueue('i');
+        assert_equals(controller.desiredSize, 1, '6 reads, 9 enqueues: desiredSize should be 1');
+        controller.enqueue('j');
+        assert_equals(controller.desiredSize, 0, '6 reads, 10 enqueues: desiredSize should be 0');
+        controller.enqueue('k');
+        assert_equals(controller.desiredSize, -1, '6 reads, 11 enqueues: desiredSize should be -1');
+
         test3.done();
-    })).catch(test3.step_func(function(e) { assert_reached(e); } ));
+    })).catch(test3.step_func(function(e) { assert_unreached(e); } ));
 });
 </script>
index 9b75542..750a9de 100644 (file)
@@ -2,11 +2,11 @@
 FAIL ReadableStream cancellation: integration test on an infinite stream derived from a random push source cancel is not implemented
 FAIL ReadableStream cancellation: cancel(reason) should pass through the given reason to the underlying source cancel is not implemented
 FAIL ReadableStream cancellation: cancel() on a locked stream should fail and not call the underlying source cancel cancel is not implemented
+FAIL ReadableStream cancellation: should fulfill promise when cancel callback went fine cancel is not implemented
 FAIL ReadableStream cancellation: returning a value from the underlying source's cancel should not affect the fulfillment value of the promise returned by the stream's cancel cancel is not implemented
-FAIL ReadableStream cancellation: if the underlying source's cancel method returns a promise, the promise returned by the stream's cancel should fulfill when that one does cancel is not implemented
+FAIL ReadableStream cancellation: should reject promise when cancel callback raises an exception cancel is not implemented
+FAIL ReadableStream cancellation: if the underlying source's cancel method returns a promise, the promise returned by the stream's cancel should fulfill when that one does (1) cancel is not implemented
+FAIL ReadableStream cancellation: if the underlying source's cancel method returns a promise, the promise returned by the stream's cancel should fulfill when that one does (2) cancel is not implemented
 FAIL ReadableStream cancellation: if the underlying source's cancel method returns a promise, the promise returned by the stream's cancel should reject when that one does cancel is not implemented
 FAIL ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called cancel is not implemented
-FAIL ReadableStream cancel should fulfill promise when cancel callback went fine cancel is not implemented
-FAIL ReadableStream cancel should reject promise when cancel callback raises an exception cancel is not implemented
-FAIL ReadableStream cancel should fulfill promise when cancel callback went fine after returning a promise cancel is not implemented
 
index b06c386..daa45fd 100644 (file)
@@ -27,7 +27,7 @@ test1.step(function() {
                 setTimeout(test1.step_func(function() {
                     cancellationFinished = true;
                     resolve();
-                }), standardTimeout);
+                }), 500);
             }));
         }
     });
@@ -37,7 +37,7 @@ test1.step(function() {
         assert_equals(cancellationFinished, false, 'it did not wait for the cancellation process to finish before closing');
         assert_greater_than(chunks.length, 0, 'at least one chunk should be read');
         for (var i = 0; i < chunks.length; i++) {
-            assert_equals(chunks[i].length, 128, "chunk should have 128 bytes");
+            assert_equals(chunks[i].length, 128, 'chunk ' + i + ' should have 128 bytes');
         }
     }), test1.step_func(function(e) { assert_unreached(e); }));
 
@@ -46,7 +46,7 @@ test1.step(function() {
             assert_equals(cancellationFinished, true, 'it returns a promise that is fulfilled when the cancellation finishes');
             test1.done();
         })).catch(test1.step_func(function(e) { assert_unreached(e); }));
-    }), standardTimeout + 50);
+    }), 1000);
 });
 
 test(function() {
@@ -91,33 +91,103 @@ test2.step(function() {
     }));
 });
 
-var test3 = async_test('ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel');
-test3.step(function() {
+var test3 = async_test('ReadableStream cancellation: should fulfill promise when cancel callback went fine');
+test3.step(function()
+{
+    var cancelReceived = false;
+    var cancelReason = new Error('I am tired of this stream, I prefer to cancel it');
+    var rs = new ReadableStream({
+        cancel: function(reason) {
+            cancelReceived = true;
+            assert_equals(reason, cancelReason, 'cancellation reason given to the underlying source should be equal to the one passed');
+        }
+    });
+
+    rs.cancel(cancelReason).then(
+        test3.step_func(function() {
+            assert_true(cancelReceived);
+            test3.done('stream was successfully cancelled');
+        }),
+        test3.step_func(function(e) {
+            assert_unreached("received error " + e)
+        }));
+});
+
+var test4 = async_test('ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel');
+test4.step(function() {
     var rs = new ReadableStream({
         cancel: function(reason) {
             return 'Hello';
         }
     });
 
-    rs.cancel().then(test3.step_func(function(v) {
+    rs.cancel().then(test4.step_func(function(v) {
         assert_equals(v, undefined, 'cancel() return value should be fulfilled with undefined');
-        test3.done();
-    }), test3.step_func(function() {
+        test4.done();
+    }), test4.step_func(function() {
         assert_unreached('cancel() return value should not be rejected');
     }));
 });
 
-var test4 = async_test('ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does');
-test4.step(function() {
+var test5 = async_test('ReadableStream cancellation: should reject promise when cancel callback raises an exception');
+test5.step(function()
+{
+    var thrownError = new Error('test');
+    var cancelCalled = false;
+
+    var rs = new ReadableStream({
+        cancel: function() {
+            cancelCalled = true;
+            throw thrownError;
+        }
+    });
+
+    rs.cancel('test').then(
+        test5.step_func(function() { assert_unreached('cancel should reject'); }),
+        test5.step_func(function(e) {
+            assert_true(cancelCalled);
+            assert_equals(e, thrownError);
+            test5.done();
+        })
+    );
+});
+
+var test6 = async_test('ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (1)');
+test6.step(function()
+{
+    var cancelReason = new Error('test');
+
+    var rs = new ReadableStream({
+        cancel: function(error) {
+            assert_equals(error, cancelReason);
+            return new Promise(test6.step_func(function(resolve, reject) {
+                setTimeout(test6.step_func(function() {
+                    resolve();
+                }), 500);
+            }))
+        }
+    })
+
+    rs.cancel(cancelReason).then(
+        test6.step_func(function() {
+            test6.done('stream successfully cancelled');
+        }),
+        test6.step_func(function(e) {
+            assert_unreached("received error " + e)
+        }))
+});
+
+var test7 = async_test('ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (2)');
+test7.step(function() {
     var resolveSourceCancelPromise;
     var sourceCancelPromiseHasFulfilled = false;
     var rs = new ReadableStream({
         cancel: function() {
-            var sourceCancelPromise = new Promise(test4.step_func(function(resolve, reject) {
+            var sourceCancelPromise = new Promise(test7.step_func(function(resolve, reject) {
                 resolveSourceCancelPromise = resolve;
             }));
 
-            sourceCancelPromise.then(test4.step_func(function() {
+            sourceCancelPromise.then(test7.step_func(function() {
                 sourceCancelPromiseHasFulfilled = true;
             }));
 
@@ -125,28 +195,32 @@ test4.step(function() {
         }
     });
 
-    rs.cancel().then(test4.step_func(function(value) {
-        assert_equals(sourceCancelPromiseHasFulfilled, true, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel');
-        assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined');
-        test4.done();
-    }), test4.step_func(function() { assert_unreached('cancel() return value should not be rejected'); } ));
 
-    setTimeout(test4.step_func(function() {
+    rs.cancel().then(
+        test7.step_func(function(value) {
+            assert_true(sourceCancelPromiseHasFulfilled, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel');
+            assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined');
+            test7.done();
+        }),
+        test7.step_func(function() { assert_unreached('cancel() return value should not be rejected'); })
+    );
+
+    setTimeout(test7.step_func(function() {
         resolveSourceCancelPromise('Hello');
-    }), standardTimeout);
+    }), 500);
 });
 
-var test5 = async_test('ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does');
-test5.step(function() {
+var test8 = async_test('ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does');
+test8.step(function() {
     var rejectSourceCancelPromise;
     var sourceCancelPromiseHasRejected = false;
     var rs = new ReadableStream({
         cancel: function() {
-            var sourceCancelPromise = new Promise(test5.step_func(function(resolve, reject) {
+            var sourceCancelPromise = new Promise(test8.step_func(function(resolve, reject) {
                 rejectSourceCancelPromise = reject;
             }));
 
-            sourceCancelPromise.catch(test5.step_func(function() {
+            sourceCancelPromise.catch(test8.step_func(function() {
                 sourceCancelPromiseHasRejected = true;
             }));
 
@@ -157,98 +231,29 @@ test5.step(function() {
     var errorInCancel = new Error('Sorry, it just wasn\'t meant to be.');
 
     rs.cancel().then(
-        test5.step_func(function() { assert_function('cancel() return value should not be rejected'); }),
-        test5.step_func(function(r) {
-            assert_equals(sourceCancelPromiseHasRejected, true, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel');
+        test8.step_func(function() { assert_unreached('cancel() return value should not be rejected'); }),
+        test8.step_func(function(r) {
+            assert_true(sourceCancelPromiseHasRejected, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel');
             assert_equals(r, errorInCancel, 'cancel() return value should be rejected with the underlying source\'s rejection reason');
-            test5.done();
-        }));
+            test8.done();
+        })
+    );
 
-    setTimeout(test5.step_func(function() {
+    setTimeout(test8.step_func(function() {
         rejectSourceCancelPromise(errorInCancel);
-    }), standardTimeout);
+    }), 500);
 });
 
-var test6 = async_test('ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called');
-test6.step(function() {
+var test9 = async_test('ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called');
+test9.step(function() {
     var rs = new ReadableStream({
         pull: function() {
             assert_unreached('pull should not have been called');
         }
     });
 
-    Promise.all([rs.cancel(), rs.getReader().closed]).then(test6.step_func(function() {
-        test6.done('pull should never have been called');
-    })).catch(test6.step_func(function(e) { assert_reached(e); } ));
-});
-
-var test7 = async_test('ReadableStream cancel should fulfill promise when cancel callback went fine');
-test7.step(function()
-{
-    var cancelReceived = false;
-    var cancelReason = "I am tired of this stream, I prefer to cancel it";
-    var rs = new ReadableStream({
-        cancel: function(reason) {
-            cancelReceived = true;
-            assert_equals(reason, cancelReason);
-        }
-    });
-    rs.cancel(cancelReason).then(
-        test7.step_func(function() {
-            assert_true(cancelReceived);
-            test7.done();
-        }),
-        test7.step_func(function(e) {
-            assert_unreached("received error " + e)
-        }));
-});
-
-var test8 = async_test('ReadableStream cancel should reject promise when cancel callback raises an exception');
-test8.step(function()
-{
-    var thrownError = undefined;
-
-    var rs = new ReadableStream({
-        cancel: function(error) {
-            thrownError = new Error(error);
-            throw thrownError;
-        }
-    });
-
-    rs.cancel("test").then(
-        test8.step_func(function() {
-            assert_unreached("cancel should fail")
-        }),
-        test8.step_func(function(e) {
-            assert_not_equals(thrownError, undefined);
-            assert_equals(e, thrownError);
-            test8.done();
-        })
-    );
-});
-
-var test9 = async_test('ReadableStream cancel should fulfill promise when cancel callback went fine after returning a promise');
-test9.step(function()
-{
-    var cancelReason = "test";
-
-    var rs = new ReadableStream({
-        cancel: function(error) {
-            assert_equals(error, cancelReason);
-            return new Promise(test9.step_func(function(resolve, reject) {
-                setTimeout(test9.step_func(function() {
-                    resolve();
-                }), standardTimeout);
-            }))
-        }
-    })
-
-    rs.cancel(cancelReason).then(
-        test9.step_func(function() {
-            test9.done();
-        }),
-        test9.step_func(function(e) {
-            assert_unreached("received error " + e)
-        }))
+    Promise.all([rs.cancel(), rs.getReader().closed]).then(test9.step_func(function() {
+        test9.done('pull should never have been called');
+    })).catch(test9.step_func(function(e) { assert_reached(e); } ));
 });
 </script>
index 37fd266..31f5f62 100644 (file)
@@ -1,25 +1,33 @@
 
 PASS ReadableStream can be constructed with no errors 
-FAIL ReadableStream instances should have the correct list of properties assert_array_equals: should have all the correct methods lengths differ, expected 6 got 5
+PASS ReadableStream can't be constructed with garbage 
+FAIL ReadableStream instances should have the correct list of properties assert_array_equals: should have all the correct methods lengths differ, expected 7 got 5
 PASS ReadableStream constructor should throw for non-function start arguments 
 PASS ReadableStream constructor can get initial garbage as cancel argument 
 PASS ReadableStream constructor can get initial garbage as pull argument 
-PASS ReadableStream constructor can get initial garbage as strategy argument 
+FAIL ReadableStream start should be called with the proper parameters assert_array_equals: the controller should have the right properties lengths differ, expected 5 got 4
+FAIL ReadableStream start controller parameter should be extensible assert_array_equals: prototype should have the right properties lengths differ, expected 5 got 4
+PASS ReadableStream should be able to call start method within prototype chain of its source 
 PASS ReadableStream start should be able to return a promise 
-PASS ReadableStream start should be able to return a promise and reject it 
+TIMEOUT ReadableStream start should be able to return a promise and reject it Test timed out
 PASS ReadableStream should be able to enqueue different objects. 
-PASS ReadableStream: if start throws an error, it should be re-thrown 
 TIMEOUT ReadableStream: if pull rejects, it should error the stream Test timed out
-FAIL ReadableStream: should not call pull until the previous pull call's promise fulfills assert_equals: after the promise returned by pull is fulfilled, pull should be called a second time expected 2 but got 1
+PASS ReadableStream: should only call pull once upon starting the stream 
+FAIL ReadableStream: should call pull when trying to read from a started, empty stream assert_equals: pull should be called again in reaction to calling read expected 2 but got 3
+PASS ReadableStream: should only call pull once on a non-empty stream read from before start fulfills 
+PASS ReadableStream: should only call pull once on a non-empty stream read from after start fulfills 
+PASS ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining 
+FAIL ReadableStream: should not call pull until the previous pull call's promise fulfills assert_equals: pull should have been called once after start, but not yet have been called a second time expected 1 but got 2
+FAIL ReadableStream: should pull after start, and after every read assert_equals: pull() should be called exactly four times expected 4 but got 1
 PASS ReadableStream: should not call pull after start if the stream is now closed 
 FAIL ReadableStream: should call pull after enqueueing from inside pull (with no read requests), if strategy allows assert_equals: pull() should have been called four times expected 4 but got 1
 PASS ReadableStream pull should be able to close a stream. 
-FAIL ReadableStream: enqueue should throw when the stream is readable but draining assert_equals: the first enqueue should return true expected (boolean) true but got (undefined) undefined
+PASS ReadableStream: enqueue should throw when the stream is readable but draining 
 PASS ReadableStream: enqueue should throw when the stream is closed 
 PASS ReadableStream: enqueue should throw the stored error when the stream is errored 
 FAIL ReadableStream: should call underlying source methods as methods releaseLock is not implemented
-FAIL ReadableStream strategies: the default strategy should return false for all but the first enqueue call assert_equals: first enqueue should return true expected (boolean) true but got (undefined) undefined
-FAIL ReadableStream strategies: the default strategy should continue returning true from enqueue if the chunks are read immediately assert_equals: first enqueue should return true expected (boolean) true but got (undefined) undefined
+FAIL ReadableStream strategies: the default strategy should give desiredSize of 1 to start, decreasing by 1 per enqueue assert_equals: expected (number) 1 but got (undefined) undefined
+FAIL ReadableStream strategies: the default strategy should continue giving desiredSize of 1 if the chunks are read immediately assert_equals: desiredSize should start at 1 expected (number) 1 but got (undefined) undefined
 PASS ReadableStream integration test: adapting a random push source 
 PASS ReadableStream integration test: adapting a sync pull source 
 
index e000184..79b0034 100644 (file)
@@ -6,13 +6,16 @@ PASS ReadableStreamReader closed should always return the same promise object
 PASS Constructing a ReadableStreamReader directly should fail if the stream is already locked (via direct construction) 
 PASS Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct construction) 
 PASS Constructing a ReadableStreamReader directly should fail if the stream is already locked (via getReader) 
-PASS Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct getReader) 
+PASS Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via getReader) 
 PASS Constructing a ReadableStreamReader directly should be OK if the stream is closed 
 PASS Constructing a ReadableStreamReader directly should be OK if the stream is errored 
 PASS Reading from a reader for an empty stream will wait until a chunk is available 
 FAIL cancel() on a reader releases the reader before calling through cancel is not implemented
 PASS closed should be fulfilled after stream is closed (.closed access before acquiring) 
 FAIL closed should be fulfilled after reader releases its lock (multiple stream locks) releaseLock is not implemented
+FAIL Multiple readers can access the stream in sequence releaseLock is not implemented
 FAIL Cannot use an already-released reader to unlock a stream again releaseLock is not implemented
+FAIL cancel() on a released reader is a no-op and does not pass through releaseLock is not implemented
 PASS Getting a second reader after erroring the stream should succeed 
+PASS Garbage-collecting a ReadableStreamReader should not unlock its stream 
 
index 358343a..989af49 100644 (file)
@@ -2,6 +2,7 @@
 <script src='../../resources/testharness.js'></script>
 <script src='../../resources/testharnessreport.js'></script>
 <script src='resources/streams-utils.js'></script>
+<script src='../../resources/gc.js'></script>
 <script>
 var ReadableStreamReader;
 
@@ -84,7 +85,7 @@ test(function() {
     var rs = new ReadableStream();
     rs.getReader(); // getReader() should be fine.
     assert_throws(new TypeError(), function() { rs.getReader(); }, 'getReader() should fail');
-}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct getReader)');
+}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via getReader)');
 
 test(function() {
     var rs = new ReadableStream({
@@ -190,36 +191,32 @@ test4.step(function() {
     }));
 });
 
-// var test5 = async_test('Multiple readers can access the stream in sequence');
-// test5.step(function() {
-//     var readCount = 0;
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             c.enqueue('a');
-//             c.enqueue('b');
-//             c.close();
-//         }
-//     });
-
-//     var reader1 = rs.getReader();
-//     reader1.read().then(test5.step_func(function(r) {
-//         assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
-//         ++readCount;
-//     }));
-//     reader1.releaseLock();
-
-//     var reader2 = rs.getReader();
-//     reader2.read().then(test5.step_func(function(r) {
-//         assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
-//         ++readCount;
-//     }));
-//     reader2.releaseLock();
-
-//     setTimeout(test5.step_func(function() {
-//         assert_equals(readCount, 2);
-//         test5.done();
-//     }), standardTimeout);
-// });
+var test5 = async_test('Multiple readers can access the stream in sequence');
+test5.step(function() {
+    var readCount = 0;
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+            c.close();
+        }
+    });
+
+    var reader1 = rs.getReader();
+    reader1.read().then(test5.step_func(function(r) {
+        assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
+        ++readCount;
+    }));
+    reader1.releaseLock();
+
+    var reader2 = rs.getReader();
+    reader2.read().then(test5.step_func(function(r) {
+        assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
+        assert_equals(++readCount, 2);
+        test5.done();
+    }));
+    reader2.releaseLock();
+});
 
 var test6 = async_test('Cannot use an already-released reader to unlock a stream again');
 test6.step(function() {
@@ -241,38 +238,32 @@ test6.step(function() {
     }));
 });
 
-// var test7 = async_test('cancel() on a released reader is a no-op and does not pass through');
-// test7.step(function() {
-//     var readCounts = 0;
-//     var cancelled = false;
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             c.enqueue('a');
-//         },
-//         cancel: function() {
-//             assert_unreached('underlying source cancel should not be called');
-//         }
-//     });
-
-//     var reader = rs.getReader();
-//     reader.releaseLock();
-//     reader.cancel().then(test7.step_func(function(v) {
-//         assert_equals(v, undefined, 'cancel() on the reader should fulfill with undefined')
-//         cancelled = true;
-//     }));
-
-//     var reader2 = rs.getReader();
-//     reader2.read().then(test7.step_func(function(r) {
-//         assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
-//         ++readCounts;
-//     }));
-
-//     setTimeout(test7.step_func(function() {
-//         assert_true(cancelled);
-//         assert_equals(readCounts, 1);
-//         test7.done();
-//     }), standardTimeout);
-// });
+var test7 = async_test('cancel() on a released reader is a no-op and does not pass through');
+test7.step(function() {
+    var cancelled = false;
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+        },
+        cancel: function() {
+            assert_unreached('underlying source cancel should not be called');
+        }
+    });
+
+    var reader = rs.getReader();
+    reader.releaseLock();
+    reader.cancel().then(test7.step_func(function(v) {
+        assert_equals(v, undefined, 'cancel() on the reader should fulfill with undefined')
+        cancelled = true;
+    }));
+
+    var reader2 = rs.getReader();
+    reader2.read().then(test7.step_func(function(r) {
+        assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
+        assert_true(cancelled);
+        test7.done();
+    }));
+});
 
 var test8 = async_test('Getting a second reader after erroring the stream should succeed');
 test8.step(function() {
@@ -312,4 +303,13 @@ test8.step(function() {
         test8.done();
     }));
 });
+
+test(function() {
+    var rs = new ReadableStream({});
+
+    rs.getReader();
+    window.gc();
+
+    assert_throws(new TypeError(), function() { rs.getReader(); }, 'old reader should still be locking the stream even after garbage collection');
+}, 'Garbage-collecting a ReadableStreamReader should not unlock its stream');
 </script>
diff --git a/LayoutTests/streams/reference-implementation/readable-stream-tee-expected.txt b/LayoutTests/streams/reference-implementation/readable-stream-tee-expected.txt
new file mode 100644 (file)
index 0000000..e75f26f
--- /dev/null
@@ -0,0 +1,12 @@
+
+FAIL ReadableStream teeing: rs.tee() returns an array of two ReadableStreams rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: should be able to read one branch to the end without affecting the other rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: values should be equal across each branch rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: errors in the source should propagate to both branches rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: canceling branch1 should not impact branch2 rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: canceling branch2 should not impact branch1 rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: canceling both branches should aggregate the cancel reasons into an array rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: failing to cancel the original stream should cause cancel() to reject on branches rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: closing the original should immediately close the branches rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+FAIL ReadableStream teeing: erroring the original should immediately error the branches rs.tee is not a function. (In 'rs.tee()', 'rs.tee' is undefined)
+
diff --git a/LayoutTests/streams/reference-implementation/readable-stream-tee.html b/LayoutTests/streams/reference-implementation/readable-stream-tee.html
new file mode 100644 (file)
index 0000000..dda69d2
--- /dev/null
@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='resources/streams-utils.js'></script>
+<script>
+test(function() {
+    var rs = new ReadableStream();
+
+    var result = rs.tee();
+
+    assert_true(Array.isArray(result), 'return value should be an array');
+    assert_equals(result.length, 2, 'array should have length 2');
+    assert_equals(result[0].constructor, ReadableStream, '0th element should be a ReadableStream');
+    assert_equals(result[1].constructor, ReadableStream, '1st element should be a ReadableStream');
+}, 'ReadableStream teeing: rs.tee() returns an array of two ReadableStreams');
+
+var test1 = async_test('ReadableStream teeing: should be able to read one branch to the end without affecting the other');
+test1.step(function() {
+    var closedCalled = false;
+    var readCalls = 0;
+    const rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+            c.close();
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    var reader1 = branch1.getReader();
+    var reader2 = branch2.getReader();
+
+    reader1.closed.then(test1.step_func(function() {
+        closedCalled = true;
+    })).catch(test1.step_func(function(e) { assert_unreached(e); }));
+    reader2.closed.then(test1.step_func(function() { assert_unreached('branch2 should not be closed'); }));
+
+    reader1.read().then(test1.step_func(function(r) {
+        assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch1 should be correct');
+        ++readCalls;
+    }));
+    reader1.read().then(test1.step_func(function(r) {
+        assert_object_equals(r, { value: 'b', done: false }, 'second chunk from branch1 should be correct');
+        ++readCalls;
+    }));
+    reader1.read().then(test1.step_func(function(r) {
+        assert_object_equals(r, { value: undefined, done: true }, 'third read() from branch1 should be done');
+        assert_true(closedCalled);
+        assert_equals(++readCalls, 4);
+        test1.done();
+    }));
+
+    reader2.read().then(test1.step_func(function(r) {
+        assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch2 should be correct');
+        ++readCalls;
+    }));
+});
+
+var test2 = async_test('ReadableStream teeing: values should be equal across each branch');
+test2.step(function() {
+    var theObject = { the: 'test object' };
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue(theObject);
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    var reader1 = branch1.getReader();
+    var reader2 = branch2.getReader();
+
+    Promise.all([reader1.read(), reader2.read()]).then(test2.step_func(function(values) {
+        assert_object_equals(values[0], values[1], 'the values should be equal');
+        test2.done();
+    }));
+});
+
+var test3 = async_test('ReadableStream teeing: errors in the source should propagate to both branches');
+test3.step(function() {
+    var closedRejects = 0;
+    var readCalls = 0;
+    var readRejects = 0;
+    var theError = new Error('boo!');
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+        },
+        pull: function() {
+            throw theError;
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    var reader1 = branch1.getReader();
+    var reader2 = branch2.getReader();
+
+    reader1.label = 'reader1';
+    reader2.label = 'reader2';
+
+    reader1.closed.catch(test3.step_func(function(e) {
+        ++closedRejects;
+        assert_equals(e, theError, 'branch1 closed promise should reject with the error');
+    }));
+    reader2.closed.catch(test3.step_func(function(e) {
+        ++closedRejects;
+        assert_equals(e, theError, 'branch2 closed promise should reject with the error');
+    }));
+
+    reader1.read().then(test3.step_func(function(r) {
+        ++readCalls;
+        assert_object_equals(r, { value: 'a', done: false }, 'should be able to read the first chunk in branch1');
+    }));
+
+    reader1.read().then(test3.step_func(function(r) {
+        ++readCalls;
+        assert_object_equals(r, { value: 'b', done: false }, 'should be able to read the second chunk in branch1');
+
+        return reader2.read().then(
+            test3.step_func(function() { assert_unreached('once the root stream has errored, you should not be able to read from branch2'); }),
+            test3.step_func(function(e) {
+                ++readRejects;
+                assert_equals(e, theError, 'branch2 read() promise should reject with the error');
+            }));
+    })).then(test3.step_func(function() {
+        return reader1.read().then(
+            test3.step_func(function() { assert_unreached('once the root stream has errored, you should not be able to read from branch1 either'); }),
+            test3.step_func(function(e) {
+                assert_equals(closedRejects, 2);
+                assert_equals(readCalls, 2);
+                assert_equals(++readRejects, 2);
+                assert_equals(e, theError, 'branch1 read() promise should reject with the error');
+                test3.done();
+            })
+        );
+    })).catch(test3.step_func(function(e) { assert_unreached(e); }));
+});
+
+var test4 = async_test('ReadableStream teeing: canceling branch1 should not impact branch2');
+test4.step(function() {
+    var branch1Read = false;
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+            c.close();
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    branch1.cancel();
+
+    readableStreamToArray(branch1).then(test4.step_func(function(chunks) {
+        assert_array_equals(chunks, [], 'branch1 should have no chunks');
+        branch1Read = true;
+    }));
+                                                       
+    readableStreamToArray(branch2).then(test4.step_func(function(chunks) {
+        assert_array_equals(chunks, ['a', 'b'], 'branch2 should have two chunks');
+        assert_true(branch1Read);
+        test4.done();
+    }));
+});
+
+var test5 = async_test('ReadableStream teeing: canceling branch2 should not impact branch1');
+test5.step(function() {
+    var branch2Read = false;
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+            c.close();
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    branch2.cancel();
+
+    readableStreamToArray(branch1).then(test5.step_func(function(chunks) {
+        assert_array_equals(chunks, ['a', 'b'], 'branch1 should have two chunks');
+        assert_true(branch2Read);
+        test5.done();
+    }));
+    readableStreamToArray(branch2).then(test5.step_func(function(chunks) {
+        assert_array_equals(chunks, [], 'branch2 should have no chunks');
+        branch2Read = true;
+    }));
+});
+
+var test6 = async_test('ReadableStream teeing: canceling both branches should aggregate the cancel reasons into an array');
+test6.step(function() {
+    var reason1 = new Error('We\'re wanted men.');
+    var reason2 = new Error('I have the death sentence on twelve systems.');
+
+    var rs = new ReadableStream({
+        cancel: function(reason) {
+            assert_array_equals(reason, [reason1, reason2], 'the cancel reason should be an array containing those from the branches');
+            test6.done();
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    branch1.cancel(reason1);
+    branch2.cancel(reason2);
+});
+
+var test7 = async_test('ReadableStream teeing: failing to cancel the original stream should cause cancel() to reject on branches');
+test7.step(function() {
+    var cancel1Rejected = false;
+    var theError = new Error('I\'ll be careful.');
+    var rs = new ReadableStream({
+        cancel: function() {
+            throw theError;
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    branch1.cancel().catch(test7.step_func(function(e) {
+        assert_equals(e, theError, 'branch1.cancel() should reject with the error');
+        cancel1Rejected = true;
+    }));
+    branch2.cancel().catch(test7.step_func(function(e) {
+        assert_equals(e, theError, 'branch2.cancel() should reject with the error');
+        assert_true(cancel1Rejected);
+        test7.done();
+    }));
+});
+
+var test8 = async_test('ReadableStream teeing: closing the original should immediately close the branches');
+test8.step(function() {
+    var reader1Closed = false;
+    var controller;
+    var rs = new ReadableStream({
+        start: function(c) {
+            controller = c;
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    var reader1 = branch1.getReader();
+    var reader2 = branch2.getReader();
+
+    reader1.closed.then(test8.step_func(function() {
+        reader1Closed = true; //branch1 should be closed
+    })).catch(test8.step_func(function(e) { assert_unreached(e); }));
+    reader2.closed.then(test8.step_func(function() {
+        assert_true(reader1Closed);
+        test8.done('branch2 should be closed');
+    })).catch(test8.step_func(function(e) { assert_unreached(e); }));
+
+    controller.close();
+});
+
+var test9 = async_test('ReadableStream teeing: erroring the original should immediately error the branches');
+test9.step(function() {
+    var reader1Rejected = false;
+    var controller;
+    var rs = new ReadableStream({
+        start(c) {
+            controller = c;
+        }
+    });
+
+    var branch = rs.tee();
+    var branch1 = branch[0];
+    var branch2 = branch[1];
+    var reader1 = branch1.getReader();
+    var reader2 = branch2.getReader();
+
+    var theError = new Error('boo!');
+
+    reader1.closed.then(
+        test9.step_func(function() { assert_unreached('branch1 should not be closed'); }),
+        test9.step_func(function(e) {
+            assert_equals(e, theError, 'branch1 should be errored with the error');
+            reader1Rejected = true;
+        })
+    );
+    reader2.closed.then(
+        test9.step_func(function() { assert_unreached('branch2 should not be closed'); }),
+        test9.step_func(function(e) {
+            assert_equals(e, theError, 'branch2 should be errored with the error');
+            assert_true(reader1Rejected);
+            test9.done();
+        })
+    );
+
+    controller.error(theError);
+});
+</script>
index d61bc90..a5dbe91 100644 (file)
@@ -1,34 +1,50 @@
 
 PASS Running templatedRSEmpty with ReadableStream (empty) 
-PASS instances have the correct methods and properties 
+FAIL instances have the correct methods and properties assert_equals: has a boolean locked getter expected "boolean" but got "undefined"
 PASS Running templatedRSEmptyReader with ReadableStream (empty) reader 
 PASS instances have the correct methods and properties 
+FAIL locked should be true assert_true: locked getter should return true expected true got undefined
+PASS read() should never settle 
+PASS two read()s should both never settle 
 PASS read() should return distinct promises each time 
 PASS getReader() again on the stream should fail 
+FAIL releasing the lock with pending read requests should throw but the read requests should stay pending releaseLock is not implemented
 FAIL releasing the lock should cause further read() calls to resolve as if the stream is closed releaseLock is not implemented
 FAIL releasing the lock should cause closed to fulfill releaseLock is not implemented
+FAIL releasing the lock should cause locked to become false releaseLock is not implemented
 FAIL canceling via the reader should cause the reader to act closed cancel is not implemented
 FAIL canceling via the stream should fail cancel is not implemented
 PASS Running templatedRSClosed with ReadableStream (closed via call in start) 
 FAIL cancel() should return a distinct fulfilled promise each time cancel is not implemented
+FAIL locked should be false assert_false: locked getter should return false expected false got undefined
 PASS getReader() should be OK 
 PASS should be able to acquire multiple readers, since they are all auto-released 
 PASS Running templatedRSClosedReader with ReadableStream (closed via call in start) reader 
 PASS read() should fulfill with { value: undefined, done: true } 
 PASS closed should fulfill with undefined 
+FAIL cancel() should return a distinct fulfilled promise each time cancel is not implemented
 PASS Running templatedRSClosed with ReadableStream (closed via cancel) 
 FAIL cancel() should return a distinct fulfilled promise each time cancel is not implemented
+FAIL locked should be false cancel is not implemented
 FAIL getReader() should be OK cancel is not implemented
 FAIL should be able to acquire multiple readers, since they are all auto-released cancel is not implemented
 PASS Running templatedRSClosedReader with ReadableStream (closed via cancel) reader 
 FAIL read() should fulfill with { value: undefined, done: true } cancel is not implemented
 FAIL closed should fulfill with undefined cancel is not implemented
+FAIL cancel() should return a distinct fulfilled promise each time cancel is not implemented
 PASS Running templatedRSErrored with ReadableStream (errored via call in start) 
 PASS getReader() should return a reader that acts errored 
+FAIL locked should be false assert_false: locked getter should return false expected false got undefined
 PASS Running templatedRSErroredSyncOnly with ReadableStream (errored via call in start) 
 FAIL cancel() should return a distinct rejected promise each time cancel is not implemented
 FAIL reader cancel() should return a distinct rejected promise each time cancel is not implemented
 PASS should be able to acquire multiple readers, since they are all auto-released 
+PASS Running templatedRSErrored with ReadableStream (errored via returning a rejected promise in start) 
+PASS getReader() should return a reader that acts errored 
+FAIL locked should be false assert_false: locked getter should return false expected false got undefined
+PASS Running templatedRSErroredReader with ReadableStream (errored via returning a rejected promise in start) reader 
+PASS closed should reject with the error 
+PASS read() should reject with the error 
 PASS Running templatedRSTwoChunksOpenReader with ReadableStream (two chunks enqueued, still open) reader 
 PASS calling read() twice without waiting will eventually give both chunks 
 PASS calling read() twice with waiting will eventually give both chunks 
@@ -37,7 +53,7 @@ FAIL cancel() after a read() should still give that single read result cancel is
 PASS Running templatedRSTwoChunksClosedReader with ReadableStream (two chunks enqueued, then closed) reader 
 PASS third read(), without waiting, should give { value: undefined, done: true } 
 PASS third read, with waiting, should give { value: undefined, done: true } 
-PASS draining the stream via read() should cause the reader closed promise to fulfill 
+FAIL draining the stream via read() should cause the reader closed promise to fulfill and locked to be false assert_false: stream should no longer be locked expected false got undefined
 FAIL releasing the lock after the stream is closed should do nothing releaseLock is not implemented
 FAIL releasing the lock should cause read() to act as if the stream is closed releaseLock is not implemented
 FAIL reader's closed property always returns the same promise releaseLock is not implemented
index ed4442c..58ff3bd 100644 (file)
@@ -10,10 +10,12 @@ function templatedRSEmpty(label, factory) {
     test(function() {
         var rs = factory();
 
+        assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter');
         assert_equals(typeof rs.cancel, 'function', 'has a cancel method');
         assert_equals(typeof rs.getReader, 'function', 'has a getReader method');
         assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method');
         assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method');
+        assert_equals(typeof rs.tee, 'function', 'has a tee method');
     }, 'instances have the correct methods and properties');
 }
 
@@ -25,6 +27,7 @@ function templatedRSClosed(label, factory) {
     test1.step(function() {
         var rs = factory();
         var promisesCount = 0;
+        var allChecked = false;
 
         var cancelPromise1 = rs.cancel();
         var cancelPromise2 = rs.cancel();
@@ -36,14 +39,22 @@ function templatedRSClosed(label, factory) {
         cancelPromise2.then(test1.step_func(function(v) {
             assert_equals(v, undefined, 'second cancel() call should fulfill with undefined');
             assert_equals(++promisesCount, 2);
+            assert_true(allChecked);
             test1.done();
         }));
         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+        allChecked = true;
     });
 
     test(function() {
         var rs = factory();
 
+        assert_false(rs.locked, 'locked getter should return false');
+    }, 'locked should be false');
+
+    test(function() {
+        var rs = factory();
+
         rs.getReader(); // getReader() should not throw.
     }, 'getReader() should be OK');
 
@@ -78,6 +89,12 @@ function templatedRSErrored(label, factory, error) {
             test1.done();
         }));
     });
+
+    test(function() {
+        var rs = factory();
+
+        assert_false(rs.locked, 'locked getter should return false');
+    }, 'locked should be false');
 };
 
 function templatedRSErroredSyncOnly(label, factory, error) {
@@ -88,6 +105,7 @@ function templatedRSErroredSyncOnly(label, factory, error) {
     test1.step(function() {
         var rs = factory();
         var promisesCount = 0;
+        var allChecked = false;
 
         var cancelPromise1 = rs.cancel();
         var cancelPromise2 = rs.cancel();
@@ -99,9 +117,11 @@ function templatedRSErroredSyncOnly(label, factory, error) {
         cancelPromise2.catch(test1.step_func(function(e) {
             assert_equals(e, error, 'second cancel() call should reject with the error');
             assert_equals(++promisesCount, 2);
+            assert_true(allChecked);
             test1.done();
         }));
         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+        allChecked = true;
     });
 
     var test2 = async_test('reader cancel() should return a distinct rejected promise each time');
@@ -109,6 +129,7 @@ function templatedRSErroredSyncOnly(label, factory, error) {
         var rs = factory();
         var reader = rs.getReader();
         var promisesCount = 0;
+        var allChecked = false;
 
         var cancelPromise1 = reader.cancel();
         var cancelPromise2 = reader.cancel();
@@ -120,9 +141,11 @@ function templatedRSErroredSyncOnly(label, factory, error) {
         cancelPromise2.catch(test2.step_func(function(e) {
             assert_equals(e, error, 'second cancel() call should reject with the error');
             assert_equals(++promisesCount, 2);
+            assert_true(allChecked);
             test2.done();
         }));
         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+        allChecked = true;
     });
 
     test(function() {
@@ -150,34 +173,40 @@ function templatedRSEmptyReader(label, factory) {
         assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method');
     }, 'instances have the correct methods and properties');
 
-    // var test1 = async_test('read() should never settle');
-    // test1.step(function() {
-    //     var { reader } = factory();
+    test(function() {
+        var { stream } = factory();
 
-    //     reader.read().then(
-    //         test1.step_func(function() { assert_unreached('read() should not fulfill'); }),
-    //         test1.step_func(function() { assert_unreached('read() should not reject'); })
-    //     );
+        assert_true(stream.locked, 'locked getter should return true');
+    }, 'locked should be true');
 
-    //     setTimeout(test1.step_func(function() { test1.done(); }), standardTimeout);
-    // });
+    var test1 = async_test('read() should never settle');
+    test1.step(function() {
+        var { reader } = factory();
 
-    // var test2 = async_test('two read()s should both never settle');
-    // test2.step(function() {
-    //     var { reader } = factory();
+        reader.read().then(
+            test1.step_func(function() { assert_unreached('read() should not fulfill'); }),
+            test1.step_func(function() { assert_unreached('read() should not reject'); })
+        );
 
-    //     reader.read().then(
-    //         test2.step_func(function() { assert_unreached('first read() should not fulfill'); }),
-    //         test2.step_func(function() { assert_unreached('first read() should not reject'); })
-    //     );
+        setTimeout(test1.step_func(function() { test1.done(); }), 1000);
+    });
 
-    //     reader.read().then(
-    //         test2.step_func(function() { assert_unreached('second read() should not fulfill'); }),
-    //         test2.step_func(function() { assert_unreached('second read() should not reject'); })
-    //     );
+    var test2 = async_test('two read()s should both never settle');
+    test2.step(function() {
+        var { reader } = factory();
 
-    //     setTimeout(test2.step_func(function() { test2.done(); }), standardTimeout);
-    // });
+        reader.read().then(
+            test2.step_func(function() { assert_unreached('first read() should not fulfill'); }),
+            test2.step_func(function() { assert_unreached('first read() should not reject'); })
+        );
+
+        reader.read().then(
+            test2.step_func(function() { assert_unreached('second read() should not fulfill'); }),
+            test2.step_func(function() { assert_unreached('second read() should not reject'); })
+        );
+
+        setTimeout(test2.step_func(function() { test2.done(); }), 1000);
+    });
 
     test(function() {
         var { reader } = factory();
@@ -191,29 +220,29 @@ function templatedRSEmptyReader(label, factory) {
         assert_throws(new TypeError(), function() { stream.getReader(); }, 'stream.getReader() should throw a TypeError');
     }, 'getReader() again on the stream should fail');
 
-    // var test3 = async_test('releasing the lock with pending read requests should throw but the read requests should stay pending');
-    // test3.step(function() {
-    //     var { reader } = factory();
+    var test3 = async_test('releasing the lock with pending read requests should throw but the read requests should stay pending');
+    test3.step(function() {
+        var { reader } = factory();
 
-    //     reader.read().then(
-    //         test3.step_func(function() { assert_unreached('first read() should not fulfill'); }),
-    //         test3.step_func(function() { assert_unreached('first read() should not reject'); })
-    //     );
+        reader.read().then(
+            test3.step_func(function() { assert_unreached('first read() should not fulfill'); }),
+            test3.step_func(function() { assert_unreached('first read() should not reject'); })
+        );
 
-    //     reader.read().then(
-    //         test3.step_func(function() { assert_unreached('second read() should not fulfill'); }),
-    //         test3.step_func(function() { assert_unreached('second read() should not reject'); })
-    //     );
+        reader.read().then(
+            test3.step_func(function() { assert_unreached('second read() should not fulfill'); }),
+            test3.step_func(function() { assert_unreached('second read() should not reject'); })
+        );
 
-    //     reader.closed.then(
-    //         test3.step_func(function() { assert_unreached('closed should not fulfill'); }),
-    //         test3.step_func(function() { assert_unreached('closed should not reject'); })
-    //     );
+        reader.closed.then(
+            test3.step_func(function() { assert_unreached('closed should not fulfill'); }),
+            test3.step_func(function() { assert_unreached('closed should not reject'); })
+        );
 
-    //     assert_throws(new TypeError(), test3.step_func(function() { reader.releaseLock(); }, 'releaseLock should throw a TypeError'));
+        assert_throws(new TypeError(), test3.step_func(function() { reader.releaseLock(); }, 'releaseLock should throw a TypeError'));
 
-    //     setTimeout(test3.step_func(function() { test3.done(); }), standardTimeout);
-    // });
+        setTimeout(test3.step_func(function() { test3.done(); }), 1000);
+    });
 
     var test4 = async_test('releasing the lock should cause further read() calls to resolve as if the stream is closed');
     test4.step(function() {
@@ -252,6 +281,13 @@ function templatedRSEmptyReader(label, factory) {
         }));
     });
 
+    test(function() {
+        var { stream, reader } = factory();
+
+        reader.releaseLock();
+        assert_false(stream.locked, 'locked getter should return false');
+    }, 'releasing the lock should cause locked to become false');
+
     var test6 = async_test('canceling via the reader should cause the reader to act closed');
     test6.step(function() {
         var { reader } = factory();
@@ -304,39 +340,38 @@ function templatedRSClosedReader(label, factory) {
         );
     });
 
-    // var test3 = async_test('cancel() should return a distinct fulfilled promise each time');
-    // test3.step(function() {
-    //     var { reader } = factory();
-    //     var promiseCount = 0;
-
-    //     var cancelPromise1 = reader.cancel();
-    //     var cancelPromise2 = reader.cancel();
-    //     var closedReaderPromise = reader.closed;
-
-    //     cancelPromise1.then(test3.step_func(function(v) {
-    //         assert_equals(v, undefined, 'first cancel() call should fulfill with undefined');
-    //         ++promiseCount;
-    //     }));
-    //     cancelPromise2.then(test3.step_func(function(v) {
-    //         assert_equals(v, undefined, 'second cancel() call should fulfill with undefined');
-    //         ++promiseCount;
-    //     }));
-    //     assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
-    //     assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
-    //     assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
-
-    //     setTimeout(test3.step_func(function() {
-    //         assert_equals(promiseCount, 2);
-    //         test3.done();
-    //     }), standardTimeout);
-    // });
+    var test3 = async_test('cancel() should return a distinct fulfilled promise each time');
+    test3.step(function() {
+        var { reader } = factory();
+        var promiseCount = 0;
+        var allChecked = false;
+
+        var cancelPromise1 = reader.cancel();
+        var cancelPromise2 = reader.cancel();
+        var closedReaderPromise = reader.closed;
+
+        cancelPromise1.then(test3.step_func(function(v) {
+            assert_equals(v, undefined, 'first cancel() call should fulfill with undefined');
+            ++promiseCount;
+        }));
+        cancelPromise2.then(test3.step_func(function(v) {
+            assert_equals(v, undefined, 'second cancel() call should fulfill with undefined');
+            assert_equals(++promiseCount, 2);
+            assert_true(allChecked);
+            test3.done();
+        }));
+        assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+        assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
+        assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
+        allChecked = true;
+    });
 };
 
 function templatedRSErroredReader(label, factory, error) {
     test(function() {
     }, 'Running templatedRSErroredReader with ' + label);
 
-    var test1 = async_test('closed should reject with the error', { timeout: 50 });
+    var test1 = async_test('closed should reject with the error');
     test1.step(function() {
         var { reader } = factory();
 
@@ -472,15 +507,16 @@ function templatedRSTwoChunksClosedReader(label, factory, chunks) {
         })).catch(test2.step_func(function(e) { assert_unreached(e); }));
     });
 
-    var test3 = async_test('draining the stream via read() should cause the reader closed promise to fulfill');
+    var test3 = async_test('draining the stream via read() should cause the reader closed promise to fulfill and locked to be false');
     test3.step(function() {
-        var { reader } = factory();
+        var { stream, reader } = factory();
         var readCalled = false;
 
         reader.closed.then(
             test3.step_func(function(v) {
                 assert_true(readCalled);
                 assert_equals(v, undefined, 'reader closed should fulfill with undefined');
+                assert_false(stream.locked, 'stream should no longer be locked');
                 test3.done();
             }),
             test3.step_func(function() { assert_unreached('reader closed should not reject'); })
@@ -615,24 +651,23 @@ templatedRSErroredSyncOnly('ReadableStream (errored via call in start)', functio
     theError
 );
 
-// FIXME: Activate these tests once Promise handling is supported in start function.
-// templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', function() {
-//     return new ReadableStream({
-//         start: function() {
-//             return Promise.reject(theError);
-//         }
-//     })},
-//     theError
-// );
-
-// templatedRSErroredReader('ReadableStream (errored via returning a rejected promise in start) reader', function() {
-//     return streamAndDefaultReader(new ReadableStream({
-//         start: function() {
-//             return Promise.reject(theError);
-//         }
-//     }))},
-//     theError
-// );
+templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', function() {
+    return new ReadableStream({
+        start: function() {
+            return Promise.reject(theError);
+        }
+    })},
+    theError
+);
+
+templatedRSErroredReader('ReadableStream (errored via returning a rejected promise in start) reader', function() {
+    return streamAndDefaultReader(new ReadableStream({
+        start: function() {
+            return Promise.reject(theError);
+        }
+    }))},
+    theError
+);
 
 var chunks = ['a', 'b'];
 
index 6c180d4..e39e064 100644 (file)
@@ -12,21 +12,33 @@ test(function() {
 }, 'ReadableStream can be constructed with no errors');
 
 test(function() {
+    assert_throws(new TypeError(), function() { new ReadableStream(null); }, 'constructor should throw when the source is null');
+}, 'ReadableStream can\'t be constructed with garbage');
+
+test(function() {
     var methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee'];
+    var properties = methods.concat(['locked']).sort();
 
     var rs = new ReadableStream();
     var proto = Object.getPrototypeOf(rs);
 
-    assert_array_equals(Object.getOwnPropertyNames(proto).sort(), methods, 'should have all the correct methods');
+    assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct methods');
 
     for (var m of methods) {
         var 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_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', 'should have be a method');
     }
 
+    var 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');
@@ -50,60 +62,132 @@ test(function() {
 }, 'ReadableStream constructor can get initial garbage as pull argument');
 
 test(function() {
-    new ReadableStream({ strategy: 2 }); // Constructor should not throw when strategy is not an object.
-}, 'ReadableStream constructor can get initial garbage as strategy argument');
+    var startCalled = false;
+    var source = {
+        start: function(controller) {
+            assert_equals(this, source, 'source is this during start');
+
+            var methods = ['close', 'enqueue', 'error', 'constructor'];
+            var properties = ['desiredSize'].concat(methods).sort();
+            var proto = Object.getPrototypeOf(controller);
+
+            assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties,
+                                'the controller should have the right properties');
+
+            for (var m of methods) {
+                var 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");
+            }
+
+            var 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, 1, 'constructor should have 1 parameter');
+            assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter');
+            assert_equals(controller.error.length, 1, 'error should have 1 parameter');
+
+            startCalled = true;
+        }
+    };
+
+    new ReadableStream(source);
+    assert_true(startCalled);
+}, 'ReadableStream start should be called with the proper parameters');
+
+test(function() {
+    var startCalled = false;
+    var source = {
+        start: function(controller) {
+            var properties = ['close', 'constructor', 'desiredSize', 'enqueue', 'error'];
+            assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
+                                'prototype should have the right properties');
+            controller.test = '';
+            assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
+                                'prototype should still have the right properties');
+            assert_not_equals(Object.getOwnPropertyNames(controller).indexOf('test'), -1,
+                              '"test" should be a property of the controller');
+
+            startCalled = true;
+        }
+    };
 
-var test1 = async_test('ReadableStream start should be able to return a promise');
+    var rs = new ReadableStream(source);
+    assert_true(startCalled);
+}, 'ReadableStream start controller parameter should be extensible');
+
+var test1 = async_test('ReadableStream should be able to call start method within prototype chain of its source');
 test1.step(function()
 {
+    function SimpleStreamSource() {
+    };
+    SimpleStreamSource.prototype = {
+        start: function() {
+            test1.done('start should be called');
+        },
+    }
+
+    new ReadableStream(new SimpleStreamSource());
+});
+
+var test2 = async_test('ReadableStream start should be able to return a promise');
+test2.step(function()
+{
     var readCalled = false;
     var rs = new ReadableStream({
         start: function(c) {
-            return new Promise(test1.step_func(function(resolve, reject) {
-                setTimeout(test1.step_func(function() {
+            return new Promise(test2.step_func(function(resolve, reject) {
+                setTimeout(test2.step_func(function() {
                     c.enqueue('a');
                     c.close();
                     resolve();
-                }), standardTimeout);
+                }), 500);
             }));
         },
     });
 
     var reader = rs.getReader();
 
-    reader.read().then(test1.step_func(function(r) {
+    reader.read().then(test2.step_func(function(r) {
         readCalled = true;
         assert_object_equals(r, { value: 'a', done: false }, 'value read should be the one enqueued');
     }));
 
-    reader.closed.then(test1.step_func(function() {
+    reader.closed.then(test2.step_func(function() {
         assert_true(readCalled);
-        test1.done('stream should close successfully');
+        test2.done('stream should close successfully');
     }));
 });
 
-var test2 = async_test('ReadableStream start should be able to return a promise and reject it');
-test2.step(function()
+var test3 = async_test('ReadableStream start should be able to return a promise and reject it', { timeout: 100 });
+test3.step(function()
 {
     var theError = new Error('rejected!');
     var rs = new ReadableStream({
         start: function() {
-            return new Promise(test2.step_func(function(resolve, reject) {
-                setTimeout(test2.step_func(function() {
+            return new Promise(test3.step_func(function(resolve, reject) {
+                setTimeout(test3.step_func(function() {
                     reject(theError);
-                }), standardTimeout);
+                }), 500);
             }));
         },
     });
 
-    rs.getReader().closed.catch(test2.step_func(function(e) {
+    rs.getReader().closed.catch(test3.step_func(function(e) {
         assert_equals(e, theError, 'promise should be rejected with the same error');
-        test2.done();
+        test3.done();
     }));
 });
 
-var test3 = async_test('ReadableStream should be able to enqueue different objects.');
-test3.step(function() {
+var test4 = async_test('ReadableStream should be able to enqueue different objects.');
+test4.step(function() {
     var readCalls = 0;
     var objects = [
     { potato: 'Give me more!'},
@@ -122,32 +206,26 @@ test3.step(function() {
 
     var reader = rs.getReader();
 
-    reader.read().then(test3.step_func(function(r) {
+    reader.read().then(test4.step_func(function(r) {
         assert_object_equals(r, { value: objects[readCalls++], done: false }, 'value read should be the one enqueued');
     }));
 
-    reader.read().then(test3.step_func(function(r) {
+    reader.read().then(test4.step_func(function(r) {
         assert_object_equals(r, { value: objects[readCalls++], done: false }, 'value read should be the one enqueued');
     }));
 
-    reader.read().then(test3.step_func(function(r) {
+    reader.read().then(test4.step_func(function(r) {
         assert_object_equals(r, { value: objects[readCalls++], done: false }, 'value read should be the one enqueued');
     }));
 
-    reader.closed.then(test3.step_func(function() {
+    reader.closed.then(test4.step_func(function() {
         assert_equals(readCalls, 3);
-        test3.done('stream should close correctly correctly');
+        test4.done('stream should close correctly correctly');
     }));
 });
 
-test(function() {
-    var error = new Error('aaaugh!!');
-
-    assert_throws(error, function() { new ReadableStream({ start() { throw error; } }) }, 'error should be re-thrown');
-}, 'ReadableStream: if start throws an error, it should be re-thrown');
-
-var test4 = async_test('ReadableStream: if pull rejects, it should error the stream', { timeout: 50 });
-test4.step(function() {
+var test5 = async_test('ReadableStream: if pull rejects, it should error the stream', { timeout: 50 });
+test5.step(function() {
     var error = new Error('pull failure');
     var rs = new ReadableStream({
         pull: function() {
@@ -160,213 +238,172 @@ test4.step(function() {
     var closed = false;
     var read = false;
 
-    reader.closed.catch(test4.step_func(function(e) {
+    reader.closed.catch(test5.step_func(function(e) {
         closed = true;
         assert_false(read);
         assert_equals(e, error, 'closed should reject with the thrown error');
     }));
 
-    reader.read().catch(test4.step_func(function(e) {
+    reader.read().catch(test5.step_func(function(e) {
         read = true;
         assert_true(closed);
         assert_equals(e, error, 'read() should reject with the thrown error');
-        test4.done();
+        test5.done();
     }));
 });
 
-// var test5 = async_test('ReadableStream: should only call pull once upon starting the stream');
-// test5.step(function() {
-//     var pullCount = 0;
-//     var startPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function() {
-//             return startPromise;
-//         },
-//         pull: function() {
-//             pullCount++;
-//         }
-//     });
-
-//     startPromise.then(test5.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called once start finishes');
-//     }));
+var test6 = async_test('ReadableStream: should only call pull once upon starting the stream');
+test6.step(function() {
+    var pullCount = 0;
+    var startPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function() {
+            return startPromise;
+        },
+        pull: function() {
+            pullCount++;
+        }
+    });
 
-//     setTimeout(test5.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called exactly once');
-//         test5.done();
-//     }), standardTimeout);
-// });
+    startPromise.then(test6.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should be called once start finishes');
+    }));
 
-// var test6 = async_test('ReadableStream: should only call pull once for a forever-empty stream, even after reading');
-// test6.step(function() {
-//     var pullCount = 0;
-//     var startPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function() {
-//             return startPromise;
-//         },
-//         pull: function() {
-//             pullCount++;
-//         }
-//     });
-
-//     startPromise.then(test6.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called once start finishes');
-//     }));
+    setTimeout(test6.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should be called exactly once');
+        test6.done();
+    }), 1000);
+});
 
-//     rs.getReader().read();
+var test7 = async_test('ReadableStream: should call pull when trying to read from a started, empty stream');
+test7.step(function() {
+    var pullCount = 0;
+    var startPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function() {
+            return startPromise;
+        },
+        pull: function(c) {
+            // Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it.
+            if (pullCount > 0) {
+                c.enqueue(pullCount);
+            }
 
-//     setTimeout(test6.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called exactly once');
-//         test6.done();
-//     }), standardTimeout);
-// });
+            ++pullCount;
+        }
+    });
 
-// var test7 = async_test('ReadableStream: should only call pull once on a non-empty stream read from before start fulfills');
-// test7.step(function() {
-//     var pullCount = 0;
-//     var startPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             c.enqueue('a');
-//             return startPromise;
-//         },
-//         pull: function() {
-//             pullCount++;
-//         }
-//     });
-
-//     startPromise.then(test7.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called once start finishes');
-//     }));
+    startPromise.then(test7.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should be called once start finishes');
 
-//     rs.getReader().read().then(test7.step_func(function(r) {
-//         assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
-//         assert_equals(pullCount, 1, 'pull should not have been called again');
-//     }));
+        var reader = rs.getReader();
+        return reader.read().then(test7.step_func(function(result) {
+            assert_equals(pullCount, 2, 'pull should be called again in reaction to calling read');
+            assert_object_equals(result, { value: 1, done: false }, 'the result read should be the one enqueued');
+            test7.done();
+        }));
+    })).catch(test7.step_func(function(e) { assert_unreached(e); }));
+});
 
-//     assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
+var test8 = async_test('ReadableStream: should only call pull once on a non-empty stream read from before start fulfills');
+test8.step(function() {
+    var pullCount = 0;
+    var startPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            return startPromise;
+        },
+        pull: function() {
+            pullCount++;
+        }
+    });
 
-//     setTimeout(test7.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called exactly once');
-//         test7.done();
-//     }), standardTimeout);
-// });
+    startPromise.then(test8.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should be called once start finishes');
+    }));
 
-// var test8 = async_test('ReadableStream: should only call pull twice on a non-empty stream read from after start fulfills');
-// test8.step(function() {
-//     var pullCount = 0;
-//     var startPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             c.enqueue('a');
-//             return startPromise;
-//         },
-//         pull: function() {
-//             pullCount++;
-//         }
-//     });
-
-//     startPromise.then(test8.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should be called once start finishes');
-
-//         rs.getReader().read().then(test8.step_func(function(r) {
-//             assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
-//             assert_equals(pullCount, 2, 'pull should be called again once read fulfills');
-//         }));
-//     }));
+    rs.getReader().read().then(test8.step_func(function(r) {
+        assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
+        assert_equals(pullCount, 1, 'pull should not have been called again');
+    }));
 
-//     assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
+    assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
 
-//     setTimeout(test8.step_func(function() {
-//         assert_equals(pullCount, 2, 'pull should be called exactly twice')
-//         test8.done();
-//     }), standardTimeout);
-// });
+    setTimeout(test8.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should be called exactly once');
+        test8.done();
+    }), 1000);
+});
 
-// var test9 = async_test('ReadableStream: should call pull in reaction to read()ing the last chunk, if not draining');
-// test9.step(function() {
-//     var pullCount = 0;
-//     var controller;
-//     var startPromise = Promise.resolve();
-//     var pullPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             controller = c;
-//             return startPromise;
-//         },
-//         pull: function() {
-//             ++pullCount;
-//             return pullPromise;
-//         }
-//     });
-
-//     var reader = rs.getReader();
-
-//     startPromise.then(test9.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should have been called once after read');
-
-//         controller.enqueue('a');
-
-//         return pullPromise.then(test9.step_func(function() {
-//             assert_equals(pullCount, 2, 'pull should have been called a second time after enqueue');
-
-//             return reader.read().then(test9.step_func(function() {
-//                 assert_equals(pullCount, 3, 'pull should have been called a third time after read');
-//             }));
-//         }));
-//     })).catch(test9.step_func(function(e) {
-//         assert_unreached(e);
-//     }));
+var test9 = async_test('ReadableStream: should only call pull once on a non-empty stream read from after start fulfills');
+test9.step(function() {
+    var pullCount = 0;
+    var startPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            return startPromise;
+        },
+        pull: function() {
+            pullCount++;
+        }
+    });
 
-//     setTimeout(test9.step_func(function() {
-//         assert_equals(pullCount, 3, 'pull should be called exactly thrice')
-//         test9.done();
-//     }), standardTimeout);
-// });
+    startPromise.then(test9.step_func(function() {
+        assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full');
 
-// var test10 = async_test('ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining');
-// test10.step(function() {
-//     var pullCount = 0;
-//     var controller;
-//     var startPromise = Promise.resolve();
-//     var pullPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             controller = c;
-//             return startPromise;
-//         },
-//         pull: function() {
-//             ++pullCount;
-//             return pullPromise;
-//         }
-//     });
-
-//     var reader = rs.getReader();
-
-//     startPromise.then(test10.step_func(function() {
-//         assert_equals(pullCount, 1, 'pull should have been called once after read');
-
-//         controller.enqueue('a');
-
-//         return pullPromise.then(test10.step_func(function() {
-//             assert_equals(pullCount, 2, 'pull should have been called a second time after enqueue');
-
-//             controller.close();
-
-//             return reader.read().then(test10.step_func(function() {
-//                 assert_equals(pullCount, 2, 'pull should not have been called a third time after read');
-//             }));
-//         }));
-//     })).catch(test10.step_func(function(e) {
-//         assert_unreached(e)
-//     }));
+        rs.getReader().read().then(test9.step_func(function(r) {
+            assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
 
-//     setTimeout(test10.step_func(function() {
-//         assert_equals(pullCount, 2, 'pull should be called exactly twice')
-//         test10.done();
-//     }), standardTimeout);
-// });
+            setTimeout(test9.step_func(function() {
+                assert_equals(pullCount, 1, 'pull should be called exactly once');
+                test9.done();
+            }), 1000);
+                                       
+        }));
+
+        assert_equals(pullCount, 1, 'calling read() should not cause pull to be called immediately');
+    }));
+});
+
+var test10 = async_test('ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining');
+test10.step(function() {
+    var pullCount = 0;
+    var controller;
+    var startPromise = Promise.resolve();
+    var pullPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function(c) {
+            controller = c;
+            return startPromise;
+        },
+        pull: function() {
+            ++pullCount;
+            return pullPromise;
+        }
+    });
+
+    var reader = rs.getReader();
+
+    startPromise.then(test10.step_func(function() {
+        assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
+
+        controller.enqueue('a');
+        assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
+
+        controller.close();
+
+        return reader.read().then(test10.step_func(function() {
+            assert_equals(pullCount, 1, 'pull should not have been called a second time after read');
+
+            setTimeout(test10.step_func(function() {
+                assert_equals(pullCount, 1, 'pull should be called exactly once');
+                test10.done();
+            }), 1000);
+        }));
+    })).catch(test10.step_func(function(e) { assert_unreached(e); }));
+});
 
 var test11 = async_test('ReadableStream: should not call pull until the previous pull call\'s promise fulfills');
 test11.step(function() {
@@ -375,12 +412,11 @@ test11.step(function() {
     var timesCalled = 0;
     var startPromise = Promise.resolve();
     var rs = new ReadableStream({
-        start: function(c) {
-            c.enqueue('a');
+        start: function() {
             return startPromise;
         },
-        pull: function() {
-            ++timesCalled;
+        pull: function(c) {
+            c.enqueue(++timesCalled);
             returnedPromise = new Promise(test11.step_func(function(r) { resolve = r; }));
             return returnedPromise;
         }
@@ -388,9 +424,10 @@ test11.step(function() {
     var reader = rs.getReader();
 
     startPromise.then(test11.step_func(function() {
-        reader.read().then(test11.step_func(function(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: 'a', done: false }, 'read() should fulfill with the enqueued value');
+        return reader.read().then(test11.step_func(function(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');
 
             setTimeout(test11.step_func(function() {
                 assert_equals(timesCalled, 1, 'after 30 ms, pull should still only have been called once');
@@ -398,61 +435,58 @@ test11.step(function() {
                 resolve();
 
                 returnedPromise.then(test11.step_func(function() {
-                    assert_equals(timesCalled, 2, 'after the promise returned by pull is fulfilled, pull should be called a second time');
+                    assert_equals(timesCalled, 2,
+                                  'after the promise returned by pull is fulfilled, pull should be called a second time');
                     test11.done();
                 }));
-            }), standardTimeout);
-        }))
-    })).catch(test11.step_func(function(e) {
-        assert_unreached(e)
-    }));
+            }), 500);
+        }));
+    })).catch(test11.step_func(function(e) { assert_unreached(e); }));
 });
 
-// var test12 = async_test('ReadableStream: should pull after start, and after every read');
-// test12.step(function() {
-//     var timesCalled = 0;
-//     var startPromise = Promise.resolve();
-//     var rs = new ReadableStream({
-//         start: function(c) {
-//             c.enqueue('a');
-//             c.enqueue('b');
-//             c.enqueue('c');
-//             return startPromise;
-//         },
-//         pull: function() {
-//             ++timesCalled;
-//         },
-//         strategy: {
-//             size: function() {
-//                 return 1;
-//             },
-//             shouldApplyBackpressure: function() {
-//                 return false;
-//             }
-//         }
-//     });
-//     var reader = rs.getReader();
-
-//     startPromise.then(test12.step_func(function() {
-//         return reader.read().then(test12.step_func(function(result1) {
-//             assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');
-
-//             return reader.read().then(test12.step_func(function(result2) {
-//                 assert_object_equals(result2, { value: 'b', done: false }, 'second chunk should be as expected');
-
-//                 return reader.read().then(test12.step_func(function(result3) {
-//                     assert_object_equals(result3, { value: 'c', done: false }, 'third chunk should be as expected');
-
-//                     setTimeout(test12.step_func(function() {
-//                         // Once for after start, and once for every read.
-//                         assert_equals(timesCalled, 4, 'pull() should be called exactly four times');
-//                         test12.done();
-//                     }), standardTimeout);
-//                 }));
-//             }));
-//         }));
-//     })).catch(test12.step_func(function(e) { assert_unreached(e); }));
-// });
+var test12 = async_test('ReadableStream: should pull after start, and after every read');
+test12.step(function() {
+    var timesCalled = 0;
+    var startPromise = Promise.resolve();
+    var rs = new ReadableStream({
+        start: function(c) {
+            c.enqueue('a');
+            c.enqueue('b');
+            c.enqueue('c');
+            return startPromise;
+        },
+        pull() {
+            ++timesCalled;
+        }
+    },
+    {
+        size: function() {
+            return 1;
+        },
+        highWaterMark: Infinity
+    });
+    var reader = rs.getReader();
+
+    startPromise.then(test12.step_func(function() {
+        return reader.read().then(test12.step_func(function(result1) {
+            assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');
+
+            return reader.read().then(test12.step_func(function(result2) {
+                assert_object_equals(result2, { value: 'b', done: false }, 'second chunk should be as expected');
+
+                return reader.read().then(test12.step_func(function(result3) {
+                    assert_object_equals(result3, { value: 'c', done: false }, 'third chunk should be as expected');
+
+                    setTimeout(test12.step_func(function() {
+                        // Once for after start, and once for every read.
+                        assert_equals(timesCalled, 4, 'pull() should be called exactly four times');
+                        test12.done();
+                    }), 1000);
+                }));
+            }));
+        }));
+    })).catch(test12.step_func(function(e) { assert_unreached(e); }));
+});
 
 var test13 = async_test('ReadableStream: should not call pull after start if the stream is now closed');
 test13.step(function() {
@@ -494,18 +528,16 @@ test14.step(function() {
         },
         pull: function(c) {
             c.enqueue(++timesCalled);
-        },
-        strategy: {
-            size: function() {
-                return 1;
-            },
-            shouldApplyBackpressure: function(size) {
-                return size > 3;
-            }
         }
+    },
+    {
+        size: function() {
+            return 1;
+        },
+        highWaterMark: 4
     });
 
-    startPromise.then(test14.step_func(function() {
+    setTimeout(test14.step_func(function() {
         // after start: size = 0, pull()
         // after enqueue(1): size = 1, pull()
         // after enqueue(2): size = 2, pull()
@@ -513,7 +545,7 @@ test14.step(function() {
         // after enqueue(4): size = 4, do not pull
         assert_equals(timesCalled, 4, 'pull() should have been called four times');
         test14.done();
-    }));
+    }), 1000);
 });
 
 var test15 = async_test('ReadableStream pull should be able to close a stream.');
@@ -534,35 +566,44 @@ test15.step(function() {
 });
 
 test(function() {
-  var rs = new ReadableStream({
-      start: function(c) {
-          assert_equals(c.enqueue('a'), true, 'the first enqueue should return true');
-          c.close();
-
-          assert_throws(new TypeError(''), function() { c.enqueue('b'); }, 'enqueue after close should throw a TypeError');
-      }
-  });
+    var startCalled = false;
+    var rs = new ReadableStream({
+        start: function(c) {
+            assert_equals(c.enqueue('a'), undefined, 'the first enqueue should return undefined');
+            c.close();
+
+            assert_throws(new TypeError(''), function() { c.enqueue('b'); }, 'enqueue after close should throw a TypeError');
+            startCalled = true;
+        }
+    });
+    assert_true(startCalled);
 }, 'ReadableStream: enqueue should throw when the stream is readable but draining');
 
 test(function() {
+    var startCalled = false;
     var rs = new ReadableStream({
         start: function(c) {
             c.close();
 
             assert_throws(new TypeError(), function() { c.enqueue('a'); }, 'enqueue after close should throw a TypeError');
+            startCalled = true;
         }
     });
+    assert_true(startCalled);
 }, 'ReadableStream: enqueue should throw when the stream is closed');
 
 test(function() {
+    var startCalled = false;
     var expectedError = new Error('i am sad');
     var rs = new ReadableStream({
         start: function(c) {
             c.error(expectedError);
 
             assert_throws(expectedError, function() { c.enqueue('a'); }, 'enqueue after error should throw that error');
+            startCalled = true;
         }
     });
+    assert_true(startCalled);
 }, 'ReadableStream: enqueue should throw the stored error when the stream is errored');
 
 var test16 = async_test('ReadableStream: should call underlying source methods as methods');
@@ -570,7 +611,6 @@ test16.step(function() {
     var startCalled = 0;
     var pullCalled = 0;
     var cancelCalled = 0;
-    var strategyCalled = 0;
 
     function Source() {
     }
@@ -591,13 +631,6 @@ test16.step(function() {
             cancelCalled++;
             assert_equals(this, theSource, 'cancel() should be called with the correct this');
         },
-
-        get strategy() {
-            // Called three times
-            strategyCalled++;
-            assert_equals(this, theSource, 'strategy getter should be called with the correct this');
-            return undefined;
-        }
     };
 
     var theSource = new Source();
@@ -611,24 +644,31 @@ test16.step(function() {
         assert_equals(startCalled, 1);
         assert_equals(pullCalled, 1);
         assert_equals(cancelCalled, 1);
-        assert_equals(strategyCalled, 3);
         test16.done();
     })).catch(test16.step_func(function(e) { assert_unreached(e); } ));
 });
 
 test(function() {
-  new ReadableStream({
-      start: function(c) {
-          assert_equals(c.enqueue('a'), true, 'first enqueue should return true');
-          assert_equals(c.enqueue('b'), false, 'second enqueue should return false');
-          assert_equals(c.enqueue('c'), false, 'third enqueue should return false');
-          assert_equals(c.enqueue('d'), false, 'fourth enqueue should return false');
-          assert_equals(c.enqueue('e'), false, 'fifth enqueue should return false');
-      }
-  });
-}, 'ReadableStream strategies: the default strategy should return false for all but the first enqueue call');
-
-var test17 = async_test('ReadableStream strategies: the default strategy should continue returning true from enqueue if the chunks are read immediately');
+    var startCalled = false;
+    new ReadableStream({
+        start: function(c) {
+            assert_equals(c.desiredSize, 1);
+            c.enqueue('a');
+            assert_equals(c.desiredSize, 0);
+            c.enqueue('b');
+            assert_equals(c.desiredSize, -1);
+            c.enqueue('c');
+            assert_equals(c.desiredSize, -2);
+            c.enqueue('d');
+            assert_equals(c.desiredSize, -3);
+            c.enqueue('e');
+            startCalled = true;
+        }
+    });
+    assert_true(startCalled);
+}, 'ReadableStream strategies: the default strategy should give desiredSize of 1 to start, decreasing by 1 per enqueue');
+
+var test17 = async_test('ReadableStream strategies: the default strategy should continue giving desiredSize of 1 if the chunks are read immediately');
 test17.step(function() {
     var controller;
     var rs = new ReadableStream({
@@ -638,24 +678,35 @@ test17.step(function() {
     });
     var reader = rs.getReader();
 
-    assert_equals(controller.enqueue('a'), true, 'first enqueue should return true');
+    assert_equals(controller.desiredSize, 1, 'desiredSize should start at 1');
+    controller.enqueue('a');
+    assert_equals(controller.desiredSize, 0, 'desiredSize should decrease to 0 after first enqueue');
 
     reader.read().then(test17.step_func(function(result1) {
         assert_object_equals(result1, { value: 'a', done: false }, 'first chunk read should be correct');
-        assert_equals(controller.enqueue('b'), true, 'second enqueue should return true');
+
+        assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the first read');
+        controller.enqueue('b');
+        assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the second enqueue');
 
         return reader.read();
     })).then(test17.step_func(function(result2) {
-        assert_object_equals(result2, { value: 'b', done: false }, 'second chunk read should be correct');
-        assert_equals(controller.enqueue('c'), true, 'third enqueue should return true');
+            assert_object_equals(result2, { value: 'b', done: false }, 'second chunk read should be correct');
 
-        return reader.read();
+            assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the second read');
+            controller.enqueue('c');
+            assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the third enqueue');
+
+            return reader.read();
     })).then(test17.step_func(function(result3) {
-        assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read should be correct');
-        assert_equals(controller.enqueue('d'), true, 'fourth enqueue should return true');
+            assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read should be correct');
 
-        test17.done();
-    })).catch(test17.step_func(function(e) { assert_unreached(e); } ));
+            assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the third read');
+            controller.enqueue('d');
+            assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the fourth enqueue');
+
+            test17.done();
+    })).catch(test17.step_func(function(e) { assert_unreached(e); }));
 });
 
 var test18 = async_test('ReadableStream integration test: adapting a random push source');
@@ -699,7 +750,7 @@ test18.step(function() {
         }
 
         test18.done();
-    }), test18.step_func(function(e) { assert_reached(e); }));
+    }), test18.step_func(function(e) { assert_unreached(e); }));
 });
 
 var test19 = async_test('ReadableStream integration test: adapting a sync pull source');
@@ -707,23 +758,22 @@ test19.step(function() {
     var rs = sequentialReadableStream(10);
 
     readableStreamToArray(rs).then(test19.step_func(function(chunks) {
-        assert_equals(rs.source.closed, true, 'source should be closed after all chunks are read');
+        assert_true(rs.source.closed, 'source should be closed after all chunks are read');
         assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
 
         test19.done();
     }));
 });
-/*
-var test20 = async_test('ReadableStream integration test: adapting an async pull source', { timeout: 50 });
-test20.step(function() {
-    var rs = sequentialReadableStream(10, { async: true });
 
-    readableStreamToArray(rs).then(test20.step_func(function(chunks) {
-        assert_equals(rs.source.closed, true, 'source should be closed after all chunks are read');
-        assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
+// var test20 = async_test('ReadableStream integration test: adapting an async pull source');
+// test20.step(function() {
+//     var rs = sequentialReadableStream(10, { async: true });
 
-        test20.done();
-    }));
-});
-*/
+//     readableStreamToArray(rs).then(test20.step_func(function(chunks) {
+//         assert_true(rs.source.closed, 'source should be closed after all chunks are read');
+//         assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
+
+//         test20.done();
+//     }));
+// });
 </script>
index d77e3bb..b1c7453 100644 (file)
@@ -1,38 +1,10 @@
 // FIXME: Remove this file when implemented in WebCore.
 
-function typeIsObject(x) {
-    return (typeof x === 'object' && x !== null) || typeof x === 'function';
-}
-
-function ByteLengthQueuingStrategy(obj) {
-    if (!typeIsObject(obj)) {
-      throw new TypeError('Parameter must be an object.');
-    }
-
-    highWaterMark = Number(obj.highWaterMark);
-
-    if (Number.isNaN(highWaterMark)) {
-      throw new TypeError('highWaterMark must be a number.');
-    }
-    if (highWaterMark < 0) {
-      throw new RangeError('highWaterMark must be nonnegative.');
-    }
-
-    this._blqsHighWaterMark = highWaterMark;
+function ByteLengthQueuingStrategy({ highWaterMark }) {
+    createDataProperty(this, 'highWaterMark', highWaterMark);
 }
 
 ByteLengthQueuingStrategy.prototype = {
-    shouldApplyBackpressure: function(queueSize) {
-        if (!typeIsObject(this)) {
-            throw new TypeError('ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure can only be applied to objects');
-        }
-        if (!Object.prototype.hasOwnProperty.call(this, '_blqsHighWaterMark')) {
-            throw new TypeError('ByteLengthQueuingStrategy.prototype.shouldApplyBackpressure can only be applied to a ' +
-                                'ByteLengthQueuingStrategy');
-        }
-
-        return queueSize > this._blqsHighWaterMark;
-    },
     size: function(chunk) {
         return chunk.byteLength;
     }
index 47f4dfd..5fc21f2 100644 (file)
@@ -1,38 +1,10 @@
 // FIXME: Remove this file when implemented in WebCore.
 
-function typeIsObject(x) {
-  return (typeof x === 'object' && x !== null) || typeof x === 'function';
-}
-
-function CountQueuingStrategy(obj) {
-    if (!typeIsObject(obj)) {
-      throw new TypeError('Parameter must be an object.');
-    }
-
-    highWaterMark = Number(obj.highWaterMark);
-
-    if (Number.isNaN(highWaterMark)) {
-      throw new TypeError('highWaterMark must be a number.');
-    }
-    if (highWaterMark < 0) {
-      throw new RangeError('highWaterMark must be nonnegative.');
-    }
-
-    this._cqsHighWaterMark = highWaterMark;
+function CountQueuingStrategy({ highWaterMark }) {
+    createDataProperty(this, 'highWaterMark', highWaterMark);
 }
 
 CountQueuingStrategy.prototype = {
-    shouldApplyBackpressure: function(queueSize) {
-        if (!typeIsObject(this)) {
-            throw new TypeError('CountQueuingStrategy.prototype.shouldApplyBackpressure can only be applied to objects');
-        }
-        if (!Object.prototype.hasOwnProperty.call(this, '_cqsHighWaterMark')) {
-            throw new TypeError('CountQueuingStrategy.prototype.shouldApplyBackpressure can only be applied to a ' +
-                                'CountQueuingStrategy');
-        }
-
-        return queueSize > this._cqsHighWaterMark;
-    },
     size: function(chunk) {
         return 1;
     }
index 83a4209..972957c 100644 (file)
@@ -1,5 +1,3 @@
-var standardTimeout = 100;
-
 function RandomPushSource(toPush) {
     this.pushed = 0;
     this.toPush = toPush;
@@ -27,24 +25,23 @@ RandomPushSource.prototype = {
             this.paused = false;
         }
 
-        var stream = this;
+        var source = this;
         function writeChunk() {
-            if (stream.paused) {
+            if (source.paused) {
                 return;
             }
 
-            stream.pushed++;
+            source.pushed++;
 
-            if (stream.toPush > 0 && stream.pushed > stream.toPush) {
-                if (stream._intervalHandle) {
-                    clearInterval(stream._intervalHandle);
-                    stream._intervalHandle = undefined;
+            if (source.toPush > 0 && source.pushed > source.toPush) {
+                if (source._intervalHandle) {
+                    clearInterval(source._intervalHandle);
+                    source._intervalHandle = undefined;
                 }
-                stream.closed = true;
-                stream.onend();
-            }
-            else {
-                stream.ondata(randomChunk(128));
+                source.closed = true;
+                source.onend();
+            } else {
+                source.ondata(randomChunk(128));
             }
         }
     },
@@ -150,7 +147,6 @@ function sequentialReadableStream(limit, options) {
                 sequentialSource.open(function(err) {
                     if (err) {
                         reject(err);
-                        return;
                     }
                     resolve();
                 });
@@ -158,20 +154,23 @@ function sequentialReadableStream(limit, options) {
         },
 
         pull: function(c) {
-            sequentialSource.read(function(err, done, chunk) {
-                if (err) {
-                    c.error(err);
-                } else if (done) {
-                    sequentialSource.close(function(err) {
-                        if (err) {
-                            c.error(err);
-                            return;
-                        }
-                        c.close();
-                    });
-                } else {
-                    c.enqueue(chunk);
-                }
+            return new Promise(function(resolve, reject) {
+                sequentialSource.read(function(err, done, chunk) {
+                    if (err) {
+                        reject(err);
+                    } else if (done) {
+                        sequentialSource.close(function(err) {
+                            if (err) {
+                                reject(err);
+                            }
+                            c.close();
+                            resolve();
+                        });
+                    } else {
+                        c.enqueue(chunk);
+                        resolve();
+                    }
+                });
             });
         },
     });
@@ -180,3 +179,15 @@ function sequentialReadableStream(limit, options) {
 
     return stream;
 };
+
+function typeIsObject(x) {
+    return (typeof x === 'object' && x !== null) || typeof x === 'function';
+}
+
+function createDataProperty(o, p, v) {
+    if (!typeIsObject(o)) {
+        throw new TypeError('Parameter must be an object.');
+        return
+    }
+    Object.defineProperty(o, p, { value: v, writable: true, enumerable: true, configurable: true });
+}