Skip to content

Commit c084df5

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 527da44 commit c084df5

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)