-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[streams] Add tests for aborting pipeTo()
Add tests for aborting ReadableStream.prototype.pipeTo using an AbortSignal. Tests correspond to PR whatwg/streams#744.
- Loading branch information
Showing
5 changed files
with
410 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,365 @@ | ||
'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'); | ||
self.importScripts('../resources/test-utils.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 = recordingReadableStream(errorOnPull, hwm0); | ||
const ws = recordingWritableStream(); | ||
return promise_rejects(t, new TypeError(), rs.pipeTo(ws, { signal: invalidSignal }), 'pipeTo should reject') | ||
.then(() => { | ||
assert_equals(rs.events.length, 0, 'no ReadableStream methods should have been called'); | ||
assert_equals(ws.events.length, 0, 'no WritableStream methods should have been called'); | ||
}); | ||
}, `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'); | ||
assert_equals(rs.events[1].constructor.name, 'DOMException', | ||
'the argument to cancel should be a DOMException'); | ||
}); | ||
}, 'an aborted signal should cause the writable stream to reject with an AbortError'); | ||
|
||
promise_test(() => { | ||
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 => { | ||
let readController; | ||
const rs = new ReadableStream({ | ||
start(c) { | ||
readController = c; | ||
c.enqueue('a'); | ||
c.enqueue('b'); | ||
} | ||
}); | ||
const abortController = new AbortController(); | ||
const signal = abortController.signal; | ||
let resolveWrite; | ||
const writePromise = new Promise(resolve => { | ||
resolveWrite = resolve; | ||
}); | ||
const ws = recordingWritableStream({ | ||
write() { | ||
return writePromise; | ||
} | ||
}, new CountQueuingStrategy({ highWaterMark: Infinity })); | ||
const pipeToPromise = rs.pipeTo(ws, { signal }); | ||
return delay(0).then(() => { | ||
abortController.abort(); | ||
readController.close(); // Make sure the test terminates when signal is not implemented. | ||
resolveWrite(); | ||
return promise_rejects(t, 'AbortError', pipeToPromise, 'pipeTo should reject'); | ||
}).then(() => { | ||
assert_equals(ws.events.length, 6, 'chunks "a" and "b" should have been written'); | ||
assert_array_equals(ws.events.slice(0, 5), ['write', 'a', 'write', 'b', 'abort'], 'events should match'); | ||
assert_equals(ws.events[5].name, 'AbortError', 'abort reason should be an AbortError'); | ||
}); | ||
}, 'all pending writes should complete on abort'); | ||
|
||
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'); | ||
|
||
promise_test(() => { | ||
let readController; | ||
const rs = new ReadableStream({ | ||
start(c) { | ||
readController = c; | ||
} | ||
}); | ||
const ws = new WritableStream(); | ||
const abortController = new AbortController(); | ||
const signal = abortController.signal; | ||
const pipeToPromise = rs.pipeTo(ws, { signal, preventClose: true }); | ||
readController.close(); | ||
return Promise.resolve().then(() => { | ||
abortController.abort(); | ||
return pipeToPromise; | ||
}).then(() => ws.getWriter().write('this should succeed')); | ||
}, 'abort should do nothing after the readable is closed'); | ||
|
||
promise_test(t => { | ||
let readController; | ||
const rs = new ReadableStream({ | ||
start(c) { | ||
readController = c; | ||
} | ||
}); | ||
const ws = new WritableStream(); | ||
const abortController = new AbortController(); | ||
const signal = abortController.signal; | ||
const pipeToPromise = rs.pipeTo(ws, { signal, preventAbort: true }); | ||
readController.error(error1); | ||
return Promise.resolve().then(() => { | ||
abortController.abort(); | ||
return promise_rejects(t, error1, pipeToPromise, 'pipeTo should reject'); | ||
}).then(() => ws.getWriter().write('this should succeed')); | ||
}, 'abort should do nothing after the readable is errored'); | ||
|
||
promise_test(t => { | ||
let readController; | ||
const rs = new ReadableStream({ | ||
start(c) { | ||
readController = c; | ||
} | ||
}); | ||
let resolveWrite; | ||
const writePromise = new Promise(resolve => { | ||
resolveWrite = resolve; | ||
}); | ||
const ws = new WritableStream({ | ||
write() { | ||
readController.error(error1); | ||
return writePromise; | ||
} | ||
}); | ||
const abortController = new AbortController(); | ||
const signal = abortController.signal; | ||
const pipeToPromise = rs.pipeTo(ws, { signal, preventAbort: true }); | ||
readController.enqueue('a'); | ||
return delay(0).then(() => { | ||
abortController.abort(); | ||
resolveWrite(); | ||
return promise_rejects(t, error1, pipeToPromise, 'pipeTo should reject'); | ||
}).then(() => ws.getWriter().write('this should succeed')); | ||
}, 'abort should do nothing after the readable is errored, even with pending writes'); | ||
|
||
promise_test(t => { | ||
const rs = recordingReadableStream({ | ||
pull(controller) { | ||
return delay(0).then(() => controller.close()); | ||
} | ||
}); | ||
let writeController; | ||
const ws = new WritableStream({ | ||
start(c) { | ||
writeController = c; | ||
} | ||
}); | ||
const abortController = new AbortController(); | ||
const signal = abortController.signal; | ||
const pipeToPromise = rs.pipeTo(ws, { signal, preventCancel: true }); | ||
return Promise.resolve().then(() => { | ||
writeController.error(error1); | ||
return Promise.resolve(); | ||
}).then(() => { | ||
abortController.abort(); | ||
return promise_rejects(t, error1, pipeToPromise, 'pipeTo should reject'); | ||
}).then(() => { | ||
assert_array_equals(rs.events, ['pull'], 'cancel should not have been called'); | ||
}); | ||
}, 'abort should do nothing after the writable is errored'); | ||
|
||
done(); |
Oops, something went wrong.