Skip to content

Commit 40a4e44

Browse files
committed
test: cover pipeTo shared write request failures
Exercise every precondition-failure branch of the new writableStreamDefaultWriterWriteWithRequest - errored, close-queued, erroring, and mismatched destinations - plus the resolve/reject settle paths and the abort-with-write-in-flight pipe ordering. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 6197899 commit 40a4e44

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Flags: --expose-internals --no-warnings
2+
'use strict';
3+
4+
// Exercises writableStreamDefaultWriterWriteWithRequest: settle paths
5+
// and the latched precondition failures.
6+
7+
const common = require('../common');
8+
const assert = require('assert');
9+
10+
const {
11+
WritableStream,
12+
WritableStreamDefaultWriter,
13+
ReadableStream,
14+
} = require('stream/web');
15+
16+
const {
17+
writableStreamDefaultWriterWriteWithRequest,
18+
} = require('internal/webstreams/writablestream');
19+
20+
function makeRequest(overrides = {}) {
21+
return {
22+
promise: null,
23+
pending: 0,
24+
failed: false,
25+
failure: undefined,
26+
resolve: common.mustNotCall('resolve'),
27+
reject: common.mustNotCall('reject'),
28+
...overrides,
29+
};
30+
}
31+
32+
{
33+
// A write to a writable destination bumps `pending` and settles through
34+
// request.resolve().
35+
const request = makeRequest({
36+
resolve: common.mustCall(function() {
37+
assert.strictEqual(this, request);
38+
assert.strictEqual(this.pending, 1);
39+
assert.strictEqual(this.failed, false);
40+
}),
41+
});
42+
const ws = new WritableStream({
43+
write: common.mustCall((chunk) => {
44+
assert.strictEqual(chunk, 'chunk');
45+
}),
46+
});
47+
const writer = new WritableStreamDefaultWriter(ws);
48+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
49+
assert.strictEqual(request.pending, 1);
50+
}
51+
52+
{
53+
// A write whose sink rejects settles through request.reject() with the
54+
// sink's error.
55+
const error = new Error('sink failure');
56+
const request = makeRequest({
57+
reject: common.mustCall(function(reason) {
58+
assert.strictEqual(this, request);
59+
assert.strictEqual(reason, error);
60+
}),
61+
});
62+
const ws = new WritableStream({
63+
write: common.mustCall(() => Promise.reject(error)),
64+
});
65+
const writer = new WritableStreamDefaultWriter(ws);
66+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
67+
assert.strictEqual(request.pending, 1);
68+
}
69+
70+
{
71+
// Writing to an errored destination latches the stored error without
72+
// queueing the write.
73+
const error = new Error('start failure');
74+
const ws = new WritableStream({
75+
start(controller) { controller.error(error); },
76+
});
77+
queueMicrotask(common.mustCall(() => {
78+
const writer = new WritableStreamDefaultWriter(ws);
79+
const request = makeRequest();
80+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
81+
assert.strictEqual(request.pending, 0);
82+
assert.strictEqual(request.failed, true);
83+
assert.strictEqual(request.failure, error);
84+
}));
85+
}
86+
87+
{
88+
// Writing to a destination with a queued close latches an
89+
// ERR_INVALID_STATE TypeError.
90+
const ws = new WritableStream({});
91+
const writer = new WritableStreamDefaultWriter(ws);
92+
writer.close().then(common.mustCall());
93+
const request = makeRequest();
94+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
95+
assert.strictEqual(request.pending, 0);
96+
assert.strictEqual(request.failed, true);
97+
assert.match(request.failure.message, /WritableStream is closed/);
98+
}
99+
100+
{
101+
// Writing to an erroring destination latches the abort reason. The
102+
// stream stays in the 'erroring' state until its start algorithm
103+
// settles, so aborting right after construction reaches it
104+
// deterministically.
105+
const reason = new Error('abort reason');
106+
const ws = new WritableStream({});
107+
const writer = new WritableStreamDefaultWriter(ws);
108+
writer.abort(reason).then(common.mustCall());
109+
const request = makeRequest();
110+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
111+
assert.strictEqual(request.pending, 0);
112+
assert.strictEqual(request.failed, true);
113+
assert.strictEqual(request.failure, reason);
114+
}
115+
116+
{
117+
// A size algorithm that detaches the writer makes the write latch a
118+
// mismatched-streams error.
119+
let writer;
120+
const ws = new WritableStream({}, {
121+
size: common.mustCall(() => {
122+
writer.releaseLock();
123+
return 1;
124+
}),
125+
highWaterMark: 1,
126+
});
127+
writer = new WritableStreamDefaultWriter(ws);
128+
const request = makeRequest();
129+
writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request);
130+
assert.strictEqual(request.pending, 0);
131+
assert.strictEqual(request.failed, true);
132+
assert.match(request.failure.message, /Mismatched WritableStreams/);
133+
}
134+
135+
{
136+
// End to end: aborting a pipe with a write in flight still waits for
137+
// the in-flight write before aborting the destination, and the pipe
138+
// rejects with an AbortError.
139+
const ac = new AbortController();
140+
const order = [];
141+
const { promise: gate, resolve: openGate } = Promise.withResolvers();
142+
let i = 0;
143+
const rs = new ReadableStream({
144+
pull(controller) { controller.enqueue(i++); },
145+
});
146+
const pipe = rs.pipeTo(new WritableStream({
147+
write: common.mustCall((chunk) => {
148+
order.push(`write:${chunk}`);
149+
ac.abort();
150+
queueMicrotask(() => {
151+
order.push('settle:0');
152+
openGate();
153+
});
154+
return gate;
155+
}),
156+
abort: common.mustCall(() => {
157+
order.push('abort');
158+
assert.deepStrictEqual(order, ['write:0', 'settle:0', 'abort']);
159+
}),
160+
}), { signal: ac.signal });
161+
assert.rejects(pipe, { name: 'AbortError' }).then(common.mustCall());
162+
}

0 commit comments

Comments
 (0)