Skip to content

Commit

Permalink
[streams] Add tests for aborting pipeTo()
Browse files Browse the repository at this point in the history
Add tests for aborting ReadableStream.prototype.pipeTo using an
AbortSignal.

Tests correspond to PR whatwg/streams#744.
  • Loading branch information
ricea committed Oct 4, 2018
1 parent 33a9abd commit 5a2e7b9
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 0 deletions.
11 changes: 11 additions & 0 deletions streams/piping/abort.dedicatedworker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>abort.js dedicated worker wrapper file</title>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script>
'use strict';
fetch_tests_from_worker(new Worker('abort.js'));
</script>
11 changes: 11 additions & 0 deletions streams/piping/abort.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>abort.js browser context wrapper file</title>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script src="../resources/recording-streams.js"></script>
<script src="../resources/test-utils.js"></script>

<script src="abort.js"></script>
239 changes: 239 additions & 0 deletions streams/piping/abort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
'use strict';

// Tests for the use of pipeTo with AbortSignal.
// There is some extra complexity to avoid timeouts in environments where abort is not implemented.

if (self.importScripts) {
self.importScripts('/resources/testharness.js');
self.importScripts('../resources/recording-streams.js');
}

const error1 = new Error('error1');
error1.name = 'error1';
const error2 = new Error('error2');
error2.name = 'error2';

const errorOnPull = {
pull(controller) {
// This will cause the test to error if pipeTo abort is not implemented.
controller.error('failed to abort');
}
};

// To stop pull() being called immediately when the stream is created, we need to set highWaterMark to 0.
const hwm0 = { highWaterMark: 0 };

for (const invalidSignal of [null, 'AbortSignal', true, -1, Object.create(AbortSignal.prototype)]) {
promise_test(t => {
const rs = new ReadableStream({
start(controller) {
controller.close();
}
});
const ws = new WritableStream();
return promise_rejects(t, new TypeError(), rs.pipeTo(ws, { signal: invalidSignal }), 'pipeTo should reject');
}, `a signal argument '${invalidSignal}' should cause pipeTo() to reject`);
}

promise_test(t => {
const rs = recordingReadableStream(errorOnPull, hwm0);
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject')
.then(() => Promise.all([
rs.getReader().closed,
promise_rejects(t, 'AbortError', ws.getWriter().closed, 'writer.closed should reject')
]))
.then(() => {
assert_equals(rs.events.length, 2, 'cancel should have been called');
assert_equals(rs.events[0], 'cancel', 'first event should be cancel');
assert_equals(rs.events[1].name, 'AbortError', 'the argument to cancel should be an AbortError');
});
}, 'an aborted signal should cause the writable stream to reject with an AbortError');

promise_test(t => {
let error;
const rs = recordingReadableStream(errorOnPull, hwm0);
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return rs.pipeTo(ws, { signal })
.catch(e => {
error = e;
})
.then(() => Promise.all([
rs.getReader().closed,
ws.getWriter().closed.catch(e => {
assert_equals(e, error, 'the writable should be errored with the same object');
})
]))
.then(() => {
assert_equals(rs.events.length, 2, 'cancel should have been called');
assert_equals(rs.events[0], 'cancel', 'first event should be cancel');
assert_equals(rs.events[1], error, 'the readable should be canceled with the same object')
});
}, 'all the AbortError objects should be the same object');

promise_test(t => {
const rs = recordingReadableStream(errorOnPull, hwm0);
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal, preventCancel: true }), 'pipeTo should reject')
.then(() => assert_equals(rs.events.length, 0, 'cancel should not be called'));
}, 'preventCancel should prevent canceling the readable');

promise_test(t => {
const rs = new ReadableStream(errorOnPull, hwm0);
const ws = recordingWritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal, preventAbort: true }), 'pipeTo should reject')
.then(() => {
assert_equals(ws.events.length, 0, 'writable should not have been aborted');
return ws.getWriter().ready;
});
}, 'preventAbort should prevent aborting the readable');

promise_test(t => {
const rs = new ReadableStream({
start(controller) {
controller.enqueue('a');
controller.enqueue('b');
controller.close();
}
});
const abortController = new AbortController();
const signal = abortController.signal;
const ws = recordingWritableStream({
write() {
abortController.abort();
}
});
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject')
.then(() => {
assert_equals(ws.events.length, 4, 'only chunk "a" should have been written');
assert_array_equals(ws.events.slice(0, 3), ['write', 'a', 'abort'], 'events should match');
assert_equals(ws.events[3].name, 'AbortError', 'abort reason should be an AbortError');
});
}, 'abort should prevent further reads');

promise_test(t => {
const rs = new ReadableStream({
pull(controller) {
controller.error('failed to abort');
},
cancel() {
return Promise.reject(error1);
}
}, hwm0);
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, error1, rs.pipeTo(ws, { signal }), 'pipeTo should reject');
}, 'a rejection from underlyingSource.cancel() should be returned by pipeTo()');

promise_test(t => {
const rs = new ReadableStream(errorOnPull, hwm0);
const ws = new WritableStream({
abort() {
return Promise.reject(error1);
}
});
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, error1, rs.pipeTo(ws, { signal }), 'pipeTo should reject');
}, 'a rejection from underlyingSink.abort() should be returned by pipeTo()');

promise_test(t => {
const events = [];
const rs = new ReadableStream({
pull(controller) {
controller.error('failed to abort');
},
cancel() {
events.push('cancel');
return Promise.reject(error1);
}
}, hwm0);
const ws = new WritableStream({
abort() {
events.push('abort');
return Promise.reject(error2);
}
});
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, error2, rs.pipeTo(ws, { signal }), 'pipeTo should reject')
.then(() => assert_array_equals(events, ['abort', 'cancel'], 'abort() should be called before cancel()'));
}, 'a rejection from underlyingSink.abort() should be preferred to one from underlyingSource.cancel()');

promise_test(t => {
const rs = new ReadableStream({
start(controller) {
controller.close();
}
});
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject');
}, 'abort signal takes priority over closed readable');

promise_test(t => {
const rs = new ReadableStream({
start(controller) {
controller.error(error1);
}
});
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject');
}, 'abort signal takes priority over errored readable');

promise_test(t => {
const rs = new ReadableStream({
pull(controller) {
controller.error('failed to abort');
}
}, hwm0);
const ws = new WritableStream();
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
const writer = ws.getWriter();
return writer.close().then(() => {
writer.releaseLock();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject');
});
}, 'abort signal takes priority over closed writable');

promise_test(t => {
const rs = new ReadableStream({
pull(controller) {
controller.error('failed to abort');
}
}, hwm0);
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
const abortController = new AbortController();
const signal = abortController.signal;
abortController.abort();
return promise_rejects(t, 'AbortError', rs.pipeTo(ws, { signal }), 'pipeTo should reject');
}, 'abort signal takes priority over errored writable');

done();
12 changes: 12 additions & 0 deletions streams/piping/abort.serviceworker.https.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>abort.js service worker wrapper file</title>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>

<script>
'use strict';
service_worker_test('abort.js', 'Service worker test setup');
</script>
11 changes: 11 additions & 0 deletions streams/piping/abort.sharedworker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>abort.js shared worker wrapper file</title>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script>
'use strict';
fetch_tests_from_worker(new SharedWorker('abort.js'));
</script>

0 comments on commit 5a2e7b9

Please sign in to comment.