<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[189696] trunk/LayoutTests</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/189696">189696</a></dd>
<dt>Author</dt> <dd>calvaris@igalia.com</dd>
<dt>Date</dt> <dd>2015-09-14 01:47:39 -0700 (Mon, 14 Sep 2015)</dd>
</dl>
<h3>Log Message</h3>
<pre>[Streams API] pipe-to writable stream tests
https://bugs.webkit.org/show_bug.cgi?id=148296
Reviewed by Darin Adler.
* streams/reference-implementation/pipe-to-expected.txt: Added.
* streams/reference-implementation/pipe-to.html: Added.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsstreamsreferenceimplementationpipetoexpectedtxt">trunk/LayoutTests/streams/reference-implementation/pipe-to-expected.txt</a></li>
<li><a href="#trunkLayoutTestsstreamsreferenceimplementationpipetohtml">trunk/LayoutTests/streams/reference-implementation/pipe-to.html</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (189695 => 189696)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2015-09-14 08:42:43 UTC (rev 189695)
+++ trunk/LayoutTests/ChangeLog        2015-09-14 08:47:39 UTC (rev 189696)
</span><span class="lines">@@ -1,5 +1,15 @@
</span><span class="cx"> 2015-09-14 Xabier Rodriguez Calvar <calvaris@igalia.com>
</span><span class="cx">
</span><ins>+ [Streams API] pipe-to writable stream tests
+ https://bugs.webkit.org/show_bug.cgi?id=148296
+
+ Reviewed by Darin Adler.
+
+ * streams/reference-implementation/pipe-to-expected.txt: Added.
+ * streams/reference-implementation/pipe-to.html: Added.
+
+2015-09-14 Xabier Rodriguez Calvar <calvaris@igalia.com>
+
</ins><span class="cx"> [Streams API] Add readable stream templated tests for writable streams
</span><span class="cx"> https://bugs.webkit.org/show_bug.cgi?id=148304
</span><span class="cx">
</span></span></pre></div>
<a id="trunkLayoutTestsstreamsreferenceimplementationpipetoexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/streams/reference-implementation/pipe-to-expected.txt (0 => 189696)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/streams/reference-implementation/pipe-to-expected.txt         (rev 0)
+++ trunk/LayoutTests/streams/reference-implementation/pipe-to-expected.txt        2015-09-14 08:47:39 UTC (rev 189696)
</span><span class="lines">@@ -0,0 +1,26 @@
</span><ins>+
+FAIL Piping from a ReadableStream from which lots of data are readable synchronously Can't find variable: WritableStream
+FAIL Piping from a ReadableStream in readable state to a WritableStream in closing state Can't find variable: WritableStream
+FAIL Piping from a ReadableStream in readable state to a WritableStream in errored state Can't find variable: WritableStream
+FAIL Piping from a ReadableStream in the readable state which becomes closed after pipeTo call to a WritableStream in the writable state Can't find variable: WritableStream
+FAIL Piping from a ReadableStream in the readable state which becomes errored after pipeTo call to a WritableStream in the writable state Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream which becomes non-empty after pipeTo call to a WritableStream in the writable state Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream which becomes errored after pipeTo call to a WritableStream in the writable state Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream to a WritableStream in the writable state which becomes errored after a pipeTo call Can't find variable: WritableStream
+FAIL Piping from a non-empty ReadableStream to a WritableStream in the waiting state which becomes writable after a pipeTo call Can't find variable: WritableStream
+FAIL Piping from a non-empty ReadableStream to a WritableStream in waiting state which becomes errored after a pipeTo call Can't find variable: WritableStream
+FAIL Piping from a non-empty ReadableStream which becomes errored after pipeTo call to a WritableStream in the waiting state Can't find variable: WritableStream
+FAIL Piping from a non-empty ReadableStream to a WritableStream in the waiting state where both become ready after a pipeTo Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream to a WritableStream in the waiting state which becomes writable after a pipeTo call Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream which becomes closed after a pipeTo call to a WritableStream in the waiting state whose writes never complete Can't find variable: WritableStream
+FAIL Piping from an empty ReadableStream which becomes errored after a pipeTo call to a WritableStream in the waiting state Can't find variable: WritableStream
+FAIL Piping to a duck-typed asynchronous "writable stream" works pipeTo is not implemented
+FAIL Piping to a stream that has been aborted passes through the error as the cancellation reason Can't find variable: WritableStream
+FAIL Piping to a stream and then aborting it passes through the error as the cancellation reason Can't find variable: WritableStream
+FAIL Piping to a stream that has been closed propagates a TypeError cancellation reason backward Can't find variable: WritableStream
+FAIL Piping to a stream and then closing it propagates a TypeError cancellation reason backward Can't find variable: WritableStream
+FAIL Piping to a stream that errors on write should pass through the error as the cancellation reason Can't find variable: WritableStream
+FAIL Piping to a stream that errors on write should not pass through the error if the stream is already closed Can't find variable: WritableStream
+FAIL Piping to a stream that errors soon after writing should pass through the error as the cancellation reason Can't find variable: WritableStream
+FAIL Piping to a writable stream that does not consume the writes fast enough exerts backpressure on the source Can't find variable: WritableStream
+
</ins></span></pre></div>
<a id="trunkLayoutTestsstreamsreferenceimplementationpipetohtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/streams/reference-implementation/pipe-to.html (0 => 189696)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/streams/reference-implementation/pipe-to.html         (rev 0)
+++ trunk/LayoutTests/streams/reference-implementation/pipe-to.html        2015-09-14 08:47:39 UTC (rev 189696)
</span><span class="lines">@@ -0,0 +1,1054 @@
</span><ins>+<!DOCTYPE html>
+<script src='../../resources/testharness.js'></script>
+<script src='../../resources/testharnessreport.js'></script>
+<script src='resources/streams-utils.js'></script>
+<script>
+var test1 = async_test('Piping from a ReadableStream from which lots of data are readable synchronously');
+test1.step(function() {
+ var events = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ for (var i = 0; i < 1000; ++i) {
+ c.enqueue(i);
+ }
+ c.close();
+ }
+ });
+
+ var ws = new WritableStream({}, new CountQueuingStrategy({ highWaterMark: 1000 }));
+
+ assert_equals(ws.state, 'writable', 'writable stream state should start out writable');
+
+ var pipeFinished = false;
+ rs.pipeTo(ws).then(
+ test1.step_func(function() {
+ pipeFinished = true;
+ rs.getReader().closed.then(test1.step_func(function() {
+ assert_equals(++events, 2);
+ }));
+ assert_equals(ws.state, 'closed', 'writable stream state should be closed after pipe finishes');
+ assert_equals(++events, 1);
+ })
+ ).catch(test1.step_func(function(e) { assert_unreached(e); }));
+
+ setTimeout(test1.step_func(function() {
+ assert_true(pipeFinished, 'pipe should have finished before a setTimeout(,0) since it should only be microtasks');
+ assert_equals(++events, 3);
+ test1.done();
+ }), 0);
+});
+
+var test2 = async_test('Piping from a ReadableStream in readable state to a WritableStream in closing state');
+test2.step(function() {
+ var events = 0;
+ var cancelReason;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('Hello');
+ },
+ cancel: function(reason) {
+ assert_throws(new TypeError(), function() { throw reason; }, 'underlying source cancel should have been called with a TypeError');
+ assert_equals(++events, 1);
+ cancelReason = reason;
+ }
+ });
+
+ var ws = new WritableStream({
+ write: function() {
+ assert_unreached('Unexpected write call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+
+ ws.close();
+ assert_equals(ws.state, 'closing', 'writable stream should be closing immediately after closing it');
+
+ rs.pipeTo(ws).then(
+ test2.step_func(function() { assert_unreached('promise returned by pipeTo should not fulfill'); }),
+ test2.step_func(function(r) {
+ assert_equals(r, cancelReason, 'the pipeTo promise should reject with the same error as the underlying source cancel was called with');
+ assert_equals(++events, 2);
+ rs.getReader().closed.then(test2.step_func(function() {
+ assert_equals(++events, 3);
+ test2.done();
+ }));
+ })
+ ).catch(test2.step_func(function(e) { assert_unreached(e); }));
+});
+
+var test3 = async_test('Piping from a ReadableStream in readable state to a WritableStream in errored state');
+test3.step(function() {
+ var pullCount = 0;
+ var cancelCalled = false;
+ var passedError = new Error('horrible things');
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('Hello');
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function(reason) {
+ assert_false(cancelCalled, 'cancel must not be called more than once');
+ cancelCalled = true;
+
+ assert_equals(reason, passedError);
+ }
+ });
+
+ var writeCalled = false;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_false(writeCalled, 'write must not be called more than once');
+ writeCalled = true;
+
+ assert_equals(chunk, 'Hello');
+
+ return Promise.reject(passedError);
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+
+ startPromise.then(test3.step_func(function() {
+ ws.write('Hello');
+ assert_true(writeCalled, 'write must be called');
+
+ ws.ready.then(test3.step_func(function() {
+ assert_equals(ws.state, 'errored', 'as a result of rejected promise, ws must be in errored state');
+
+ rs.pipeTo(ws).catch(test3.step_func(function(e) {
+ assert_equals(e, passedError, 'pipeTo promise should be rejected with the error');
+ assert_true(cancelCalled, 'cancel should have been called');
+ test3.done();
+ }));
+ }));
+ }));
+});
+
+var test4 = async_test('Piping from a ReadableStream in the readable state which becomes closed after pipeTo call to a WritableStream in the writable state');
+test4.step(function() {
+ var closeReadableStream;
+ var pullCount = 0;
+ var events = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('Hello');
+ closeReadableStream = c.close.bind(c);
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var writeCalled = false;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ if (!writeCalled) {
+ assert_equals(chunk, 'Hello', 'chunk written to writable stream should be the one enqueued into the readable stream');
+ writeCalled = true;
+ assert_equals(++events, 2);
+ } else {
+ assert_unreached('Unexpected extra write call');
+ }
+ },
+ close: function() {
+ assert_equals(pullCount, 1, 'underlying source pull should have been called once');
+ assert_equals(++events, 3);
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+
+ startPromise.then(test4.step_func(function() {
+ rs.pipeTo(ws).then(test4.step_func(function() {
+ assert_equals(ws.state, 'closed', 'writable stream should be closed after pipeTo completes');
+ assert_equals(++events, 4);
+ test4.done();
+ }));
+
+ assert_equals(ws.state, 'writable', 'writable stream should still be writable immediately after pipeTo');
+ assert_equals(++events, 1);
+
+ closeReadableStream();
+ }));
+});
+
+var test5 = async_test('Piping from a ReadableStream in the readable state which becomes errored after pipeTo call to a WritableStream in the writable state');
+test5.step(function() {
+ var errorReadableStream;
+ var pullCount = 0;
+ var events = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('Hello');
+ errorReadableStream = c.error.bind(c);
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var passedError = new Error('horrible things');
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_unreached('Unexpected extra write call');
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function(reason) {
+ assert_equals(reason, passedError, 'underlying sink abort should receive the error from the readable stream');
+ assert_equals(pullCount, 1, 'underlying source pull should have been called once');
+ assert_equals(++events, 2);
+ }
+ });
+
+ startPromise.then(test5.step_func(function() {
+ rs.pipeTo(ws).catch(test5.step_func(function(e) {
+ assert_equals(e, passedError, 'pipeTo should be rejected with the passed error');
+ assert_equals(ws.state, 'errored', 'writable stream should be errored after pipeTo completes');
+ assert_equals(++events, 3);
+ test5.done();
+ }));
+
+ assert_equals(ws.state, 'writable', 'writable stream should still be writable immediately after pipeTo');
+ assert_equals(++events, 1);
+
+ errorReadableStream(passedError);
+ }));
+});
+
+var test6 = async_test('Piping from an empty ReadableStream which becomes non-empty after pipeTo call to a WritableStream in the writable state');
+test6.step(function() {
+ var controller;
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ controller = c;
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var ws = new WritableStream({
+ write: function(chunk) {
+ assert_equals(chunk, 'Hello', 'underlying sink write should be called with the single chunk');
+ assert_equals(pullCount, 1, 'pull should have been called once');
+ test6.done();
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function(reason) {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+
+ rs.pipeTo(ws).then(test6.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }));
+ assert_equals(ws.state, 'writable', 'writable stream should start in writable state');
+
+ controller.enqueue('Hello');
+});
+
+var test7 = async_test('Piping from an empty ReadableStream which becomes errored after pipeTo call to a WritableStream in the writable state');
+test7.step(function() {
+ var errorReadableStream;
+ var abortCalled = false;
+ var rs = new ReadableStream({
+ start: function(c) {
+ errorReadableStream = c.error.bind(c);
+ },
+ pull: function() {
+ assert_unreached('Unexpected pull call');
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var passedError = new Error('horrible things');
+ var ws = new WritableStream({
+ write: function() {
+ assert_unreached('Unexpected write call');
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function(reason) {
+ assert_equals(reason, passedError, 'underlying sink abort should receive the error from the readable stream');
+ abortCalled = true;
+ }
+ });
+
+ rs.pipeTo(ws).catch(test7.step_func(function(e) {
+ assert_equals(e, passedError, 'pipeTo should reject with the passed error');
+ assert_true(abortCalled);
+ test7.done();
+ }));
+ assert_equals(ws.state, 'writable', 'writable stream should start out writable');
+ errorReadableStream(passedError);
+});
+
+var test8 = async_test('Piping from an empty ReadableStream to a WritableStream in the writable state which becomes errored after a pipeTo call');
+test8.step(function() {
+ var cancelCalled = false;
+
+ var theError = new Error('cancel with me!');
+
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function(reason) {
+ assert_equals(reason, theError, 'underlying source cancellation reason should be the writable stream error');
+ assert_equals(pullCount, 2, 'pull should have been called twice by cancel-time');
+ cancelCalled = true;
+ }
+ });
+
+ var errorWritableStream;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function(error) {
+ errorWritableStream = error;
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_unreached('Unexpected write call');
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+
+ startPromise.then(test8.step_func(function() {
+ assert_equals(ws.state, 'writable', 'ws should start writable');
+
+ rs.pipeTo(ws).catch(test8.step_func(function(e) {
+ assert_equals(e, theError, 'pipeTo should reject with the passed error');
+ assert_true(cancelCalled);
+ test8.done();
+ }));
+ assert_equals(ws.state, 'writable', 'ws should be writable after pipe');
+
+ errorWritableStream(theError);
+ assert_equals(ws.state, 'errored', 'ws should be errored after erroring it');
+ }));
+});
+
+var test9 = async_test('Piping from a non-empty ReadableStream to a WritableStream in the waiting state which becomes writable after a pipeTo call');
+test9.step(function() {
+ var pullCount = 0;
+ var readyFulfilled = false;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('World');
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var resolveWritePromise;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ if (!resolveWritePromise) {
+ assert_equals(chunk, 'Hello', 'the first chunk to arrive in write should be the first chunk written');
+ return new Promise(test9.step_func(function(resolve) { resolveWritePromise = resolve; }));
+ } else {
+ assert_equals(chunk, 'World', 'the second chunk to arrive in write should be from the readable stream');
+ assert_equals(pullCount, 1, 'the readable stream\'s pull should have been called once');
+ assert_true(readyFulfilled);
+ test9.done();
+ }
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test9.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'writable stream state should start waiting');
+
+ rs.pipeTo(ws);
+ assert_equals(ws.state, 'waiting', 'writable stream state should still be waiting immediately after piping');
+
+ resolveWritePromise();
+ ws.ready.then(test9.step_func(function() {
+ assert_equals(ws.state, 'writable', 'writable stream should eventually become writable (when ready fulfills)');
+ readyFulfilled = true;
+ })).catch(test9.step_func(function(e) { assert_unreached(e); }));
+ }));
+});
+
+var test10 = async_test('Piping from a non-empty ReadableStream to a WritableStream in waiting state which becomes errored after a pipeTo call');
+test10.step(function() {
+ var writeCalled = false;
+
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('World');
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_true(writeCalled);
+ assert_equals(pullCount, 1);
+ test10.done();
+ }
+ });
+
+ var errorWritableStream;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function(error) {
+ errorWritableStream = error;
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_false(writeCalled);
+ assert_equals(chunk, 'Hello');
+ writeCalled = true;
+ return new Promise(test10.step_func(function() { }));
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test10.step_func(function() {
+ assert_equals(ws.state, 'waiting');
+
+ rs.pipeTo(ws);
+ assert_equals(ws.state, 'waiting');
+
+ errorWritableStream();
+ assert_equals(ws.state, 'errored');
+ }));
+});
+
+var test11 = async_test('Piping from a non-empty ReadableStream which becomes errored after pipeTo call to a WritableStream in the waiting state');
+test11.step(function() {
+ var errorReadableStream;
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('World');
+ errorReadableStream = c.error.bind(c);
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var writeCalled = false;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_false(writeCalled);
+ writeCalled = true;
+
+ assert_equals(chunk, 'Hello');
+
+ return new Promise(test11.step_func(function() { }));
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ test11.done();
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test11.step_func(function() {
+ assert_equals(ws.state, 'waiting');
+ assert_equals(pullCount, 0);
+
+ rs.pipeTo(ws);
+ assert_equals(ws.state, 'waiting');
+
+ errorReadableStream();
+ }));
+});
+
+var test12 = async_test('Piping from a non-empty ReadableStream to a WritableStream in the waiting state where both become ready after a pipeTo');
+test12.step(function() {
+ var controller;
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ controller = c;
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var writeCount = 0;
+ var resolveWritePromise;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ ++writeCount;
+
+ if (writeCount === 1) {
+ assert_equals(chunk, 'Hello', 'first chunk written should equal the one passed to ws.write');
+ return new Promise(test12.step_func(function(resolve) { resolveWritePromise = resolve; }));
+ }
+ if (writeCount === 2) {
+ assert_equals(chunk, 'Goodbye', 'second chunk written should be from the source readable stream');
+ test12.done();
+ }
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function(reason) {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test12.step_func(function() {
+ assert_equals(writeCount, 1, 'exactly one write should have happened');
+ assert_equals(ws.state, 'waiting', 'writable stream should be waiting');
+
+ assert_equals(pullCount, 1, 'pull should have been called only once');
+ rs.pipeTo(ws);
+
+ controller.enqueue('Goodbye');
+
+ // Check that nothing happens before calling resolveWritePromise(), and then call resolveWritePromise()
+ // to check that pipeTo is woken up.
+ assert_equals(pullCount, 1, 'after the pipeTo and enqueue, pull still should have been called only once');
+ resolveWritePromise();
+ }));
+});
+
+var test13 = async_test('Piping from an empty ReadableStream to a WritableStream in the waiting state which becomes writable after a pipeTo call');
+test13.step(function() {
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var resolveWritePromise;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ assert_true(!resolveWritePromise);
+ assert_equals(chunk, 'Hello');
+ return new Promise(test13.step_func(function(resolve) { resolveWritePromise = resolve; }));
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test13.step_func(function() {
+ assert_equals(ws.state, 'waiting');
+
+ rs.pipeTo(ws);
+ assert_equals(ws.state, 'waiting');
+ assert_equals(pullCount, 1);
+
+ resolveWritePromise();
+ setTimeout(test13.step_func(function() {
+ assert_equals(pullCount, 2);
+
+ test13.done();
+ }), 500);
+ }));
+});
+
+var test14 = async_test('Piping from an empty ReadableStream which becomes closed after a pipeTo call to a WritableStream in the waiting state whose writes never complete');
+test14.step(function() {
+ var closeReadableStream;
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ closeReadableStream = c.close.bind(c);
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var writeCalled = false;
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ if (!writeCalled) {
+ assert_equals(chunk, 'Hello', 'the chunk should be written to the writable stream');
+ writeCalled = true;
+ closeReadableStream();
+ } else {
+ assert_unreached('Unexpected extra write call');
+ }
+ return new Promise(test14.step_func(function() { }));
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function() {
+ assert_unreached('Unexpected abort call');
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test14.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'the writable stream should be in the waiting state after starting');
+
+ rs.pipeTo(ws);
+
+ setTimeout(test14.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'the writable stream should still be waiting since the write never completed');
+ assert_equals(pullCount, 1, 'pull should have been called only once');
+ assert_true(writeCalled);
+ test14.done();
+ }), 500);
+ }));
+});
+
+var test15 = async_test('Piping from an empty ReadableStream which becomes errored after a pipeTo call to a WritableStream in the waiting state');
+test15.step(function() {
+ var errorReadableStream;
+ var pullCount = 0;
+ var rs = new ReadableStream({
+ start: function(c) {
+ errorReadableStream = c.error.bind(c);
+ },
+ pull: function() {
+ ++pullCount;
+ },
+ cancel: function() {
+ assert_unreached('Unexpected cancel call');
+ }
+ });
+
+ var writeCalled = false;
+ var passedError = new Error('horrible things');
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ if (!writeCalled) {
+ assert_equals(chunk, 'Hello');
+ writeCalled = true;
+ } else {
+ assert_unreached('Unexpected extra write call');
+ }
+ return new Promise(test15.step_func(function() { }));
+ },
+ close: function() {
+ assert_unreached('Unexpected close call');
+ },
+ abort: function(reason) {
+ assert_equals(reason, passedError);
+ assert_true(writeCalled);
+ assert_equals(pullCount, 1);
+ test15.done();
+ }
+ });
+ ws.write('Hello');
+
+ startPromise.then(test15.step_func(function() {
+ assert_equals(ws.state, 'waiting');
+
+ rs.pipeTo(ws);
+
+ errorReadableStream(passedError);
+ }));
+});
+
+var test16 = async_test('Piping to a duck-typed asynchronous "writable stream" works');
+test16.step(function() {
+ var rs = sequentialReadableStream(5, { async: true });
+
+ var chunksWritten = [];
+ var dest = {
+ state: 'writable',
+ write: function(chunk) {
+ chunksWritten.push(chunk);
+ return Promise.resolve();
+ },
+ get ready() {
+ return Promise.resolve();
+ },
+ close: function() {
+ assert_array_equals(chunksWritten, [1, 2, 3, 4, 5]);
+ test16.done();
+ return Promise.resolve();
+ },
+ abort: function() {
+ assert_unreached('Should not call abort');
+ },
+ closed: new Promise(test16.step_func(function() { }))
+ };
+
+ rs.pipeTo(dest);
+});
+
+var test17 = async_test('Piping to a stream that has been aborted passes through the error as the cancellation reason');
+test17.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ cancel: function(reason) {
+ recordedReason = reason;
+ }
+ });
+
+ var ws = new WritableStream();
+ var passedReason = new Error('I don\'t like you.');
+ ws.abort(passedReason);
+
+ rs.pipeTo(ws).catch(test17.step_func(function(e) {
+ assert_equals(e, passedReason, 'pipeTo rejection reason should be the cancellation reason');
+ assert_equals(recordedReason, passedReason, 'the recorded cancellation reason must be the passed abort reason');
+ test17.done();
+ }));
+});
+
+var test18 = async_test('Piping to a stream and then aborting it passes through the error as the cancellation reason');
+test18.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ cancel: function(reason) {
+ recordedReason = reason;
+ }
+ });
+
+ var ws = new WritableStream();
+ var passedReason = new Error('I don\'t like you.');
+
+ var pipeToPromise = rs.pipeTo(ws);
+ ws.abort(passedReason);
+
+ pipeToPromise.catch(test18.step_func(function(e) {
+ assert_equals(e, passedReason, 'pipeTo rejection reason should be the abortion reason');
+ assert_equals(recordedReason, passedReason, 'the recorded cancellation reason must be the passed abort reason');
+ test18.done();
+ }));
+});
+
+var test19 = async_test('Piping to a stream that has been closed propagates a TypeError cancellation reason backward');
+test19.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ cancel: function(reason) {
+ recordedReason = reason;
+ }
+ });
+
+ var ws = new WritableStream();
+ ws.close();
+
+ rs.pipeTo(ws).catch(test19.step_func(function(e) {
+ assert_throws(new TypeError(), function() { throw e; }, 'the rejection reason for the pipeTo promise should be a TypeError');
+ assert_throws(new TypeError(), function() { throw recordedReason; }, 'the recorded cancellation reason should be a TypeError');
+ test19.done();
+ }));
+});
+
+var test20 = async_test('Piping to a stream and then closing it propagates a TypeError cancellation reason backward');
+test20.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ cancel: function(reason) {
+ recordedReason = reason;
+ }
+ });
+
+ var ws = new WritableStream();
+
+ var pipeToPromise = rs.pipeTo(ws);
+ ws.close();
+
+ pipeToPromise.catch(test20.step_func(function(e) {
+ assert_throws(new TypeError(), function() { throw e; }, 'the rejection reason for the pipeTo promise should be a TypeError');
+ assert_throws(new TypeError(), function() { throw recordedReason; }, 'the recorded cancellation reason should be a TypeError');
+ test20.done();
+ }));
+});
+
+var test21 = async_test('Piping to a stream that errors on write should pass through the error as the cancellation reason');
+test21.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ },
+ cancel: function(reason) {
+ assert_equals(reason, passedError, 'the recorded cancellation reason must be the passed error');
+ test21.done();
+ }
+ });
+
+ var written = 0;
+ var passedError = new Error('I don\'t like you.');
+ var ws = new WritableStream({
+ write: function(chunk) {
+ return new Promise(test21.step_func(function(resolve, reject) {
+ if (++written > 1) {
+ reject(passedError);
+ } else {
+ resolve();
+ }
+ }));
+ }
+ });
+
+ rs.pipeTo(ws);
+});
+
+var test22 = async_test('Piping to a stream that errors on write should not pass through the error if the stream is already closed');
+test22.step(function() {
+ var cancelCalled = false;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ c.close();
+ },
+ cancel: function() {
+ cancelCalled = true;
+ }
+ });
+
+ var written = 0;
+ var passedError = new Error('I don\'t like you.');
+ var ws = new WritableStream({
+ write: function(chunk) {
+ return new Promise(test22.step_func(function(resolve, reject) {
+ if (++written > 1) {
+ reject(passedError);
+ } else {
+ resolve();
+ }
+ }));
+ }
+ });
+
+ rs.pipeTo(ws).then(
+ test22.step_func(function() { assert_unreached('pipeTo should not fulfill'); }),
+ test22.step_func(function(r) {
+ assert_equals(r, passedError, 'pipeTo should reject with the same error as the write');
+ assert_equals(cancelCalled, false, 'cancel should not have been called');
+ test22.done();
+ })
+ );
+});
+
+var test23 = async_test('Piping to a stream that errors soon after writing should pass through the error as the cancellation reason');
+test23.step(function() {
+ var recordedReason;
+ var rs = new ReadableStream({
+ start: function(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ },
+ cancel: function(reason) {
+ assert_equals(reason, passedError, 'the recorded cancellation reason must be the passed error');
+ test23.done();
+ }
+ });
+
+ var written = 0;
+ var passedError = new Error('I don\'t like you.');
+ var ws = new WritableStream({
+ write: function(chunk) {
+ return new Promise(test23.step_func(function(resolve, reject) {
+ if (++written > 1) {
+ setTimeout(test23.step_func(function() { reject(passedError); }), 200);
+ } else {
+ resolve();
+ }
+ }));
+ }
+ });
+
+ rs.pipeTo(ws);
+});
+
+var test24 = async_test('Piping to a writable stream that does not consume the writes fast enough exerts backpressure on the source');
+test24.step(function() {
+ var desiredSizes = [];
+ var rs = new ReadableStream({
+ start: function(c) {
+ setTimeout(test24.step_func(function() { enqueue('a'); }), 200);
+ setTimeout(test24.step_func(function() { enqueue('b'); }), 400);
+ setTimeout(test24.step_func(function() { enqueue('c'); }), 600);
+ setTimeout(test24.step_func(function() { enqueue('d'); }), 800);
+ setTimeout(test24.step_func(function() { c.close(); }), 1000);
+
+ function enqueue(chunk) {
+ c.enqueue(chunk);
+ desiredSizes.push(c.desiredSize);
+ }
+ }
+ });
+
+ var chunksGivenToWrite = [];
+ var chunksFinishedWriting = [];
+ var startPromise = Promise.resolve();
+ var ws = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ chunksGivenToWrite.push(chunk);
+ return new Promise(test24.step_func(function(resolve) {
+ setTimeout(test24.step_func(function() {
+ chunksFinishedWriting.push(chunk);
+ resolve();
+ }), 700);
+ }));
+ }
+ });
+
+ startPromise.then(test24.step_func(function() {
+ rs.pipeTo(ws).then(test24.step_func(function() {
+ assert_array_equals(desiredSizes, [1, 1, 0, -1], 'backpressure was correctly exerted at the source');
+ assert_array_equals(chunksFinishedWriting, ['a', 'b', 'c', 'd'], 'all chunks were written');
+ test24.done();
+ }));
+
+ assert_equals(ws.state, 'writable', 'at t = 0 ms, ws should be writable');
+
+ setTimeout(test24.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'at t = 250 ms, ws should be waiting');
+ assert_array_equals(chunksGivenToWrite, ['a'], 'at t = 250 ms, ws.write should have been called with one chunk');
+ assert_array_equals(chunksFinishedWriting, [], 'at t = 250 ms, no chunks should have finished writing');
+
+ // When 'a' (the very first chunk) was enqueued, it was immediately used to fulfill the outstanding read request
+ // promise, leaving room in the queue
+ assert_array_equals(desiredSizes, [1], 'at t = 250 ms, the one enqueued chunk in rs did not cause backpressure');
+ }), 250);
+
+ setTimeout(test24.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'at t = 450 ms, ws should be waiting');
+ assert_array_equals(chunksGivenToWrite, ['a'], 'at t = 450 ms, ws.write should have been called with one chunk');
+ assert_array_equals(chunksFinishedWriting, [], 'at t = 450 ms, no chunks should have finished writing');
+
+ // When 'b' was enqueued at 200 ms, the queue was also empty, since immediately after enqueuing 'a' at
+ // t = 100 ms, it was dequeued in order to fulfill the read() call that was made at time t = 0.
+ assert_array_equals(desiredSizes, [1, 1], 'at t = 450 ms, the two enqueued chunks in rs did not cause backpressure');
+ }), 450);
+
+ setTimeout(test24.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'at t = 650 ms, ws should be waiting');
+ assert_array_equals(chunksGivenToWrite, ['a'], 'at t = 650 ms, ws.write should have been called with one chunk');
+ assert_array_equals(chunksFinishedWriting, [], 'at t = 650 ms, no chunks should have finished writing');
+
+ // When 'c' was enqueued at 300 ms, the queue was again empty, since at time t = 200 ms when 'b' was enqueued,
+ // it was immediately dequeued in order to fulfill the second read() call that was made at time t = 0.
+ // However, this time there was no pending read request to whisk it away, so after the enqueue desired size is 0.
+ assert_array_equals(desiredSizes, [1, 1, 0], 'at t = 650 ms, the three enqueued chunks in rs did not cause backpressure');
+ }), 650);
+
+ setTimeout(test24.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'at t = 850 ms, ws should be waiting');
+ assert_array_equals(chunksGivenToWrite, ['a'], 'at t = 850 ms, ws.write should have been called with one chunk');
+ assert_array_equals(chunksFinishedWriting, [], 'at t = 850 ms, no chunks should have finished writing');
+
+ // When 'd' was enqueued at 400 ms, the queue was *not* empty. 'c' was still in it, since the write() of 'b' will
+ // not finish until t = 100 ms + 350 ms = 450 ms. Thus backpressure should have been exerted.
+ assert_array_equals(desiredSizes, [1, 1, 0, -1], 'at t = 850 ms, the fourth enqueued chunks in rs did cause backpressure');
+ }), 850);
+
+ setTimeout(test24.step_func(function() {
+ assert_equals(ws.state, 'waiting', 'at t = 950 ms, ws should be waiting');
+ assert_array_equals(chunksGivenToWrite, ['a', 'b'], 'at t = 950 ms, ws.write should have been called with two chunks');
+ assert_array_equals(chunksFinishedWriting, ['a'], 'at t = 950 ms, one chunk should have finished writing');
+ }), 950);
+ }));
+});
+</script>
</ins></span></pre>
</div>
</div>
</body>
</html>