1'use strict'; 2 3const common = require('../common'); 4const { Writable, addAbortSignal } = require('stream'); 5const assert = require('assert'); 6 7{ 8 const write = new Writable({ 9 write(chunk, enc, cb) { cb(); } 10 }); 11 12 write.on('finish', common.mustNotCall()); 13 write.on('close', common.mustCall()); 14 15 write.destroy(); 16 assert.strictEqual(write.destroyed, true); 17} 18 19{ 20 const write = new Writable({ 21 write(chunk, enc, cb) { 22 this.destroy(new Error('asd')); 23 cb(); 24 } 25 }); 26 27 write.on('error', common.mustCall()); 28 write.on('finish', common.mustNotCall()); 29 write.end('asd'); 30 assert.strictEqual(write.destroyed, true); 31} 32 33{ 34 const write = new Writable({ 35 write(chunk, enc, cb) { cb(); } 36 }); 37 38 const expected = new Error('kaboom'); 39 40 write.on('finish', common.mustNotCall()); 41 write.on('close', common.mustCall()); 42 write.on('error', common.mustCall((err) => { 43 assert.strictEqual(err, expected); 44 })); 45 46 write.destroy(expected); 47 assert.strictEqual(write.destroyed, true); 48} 49 50{ 51 const write = new Writable({ 52 write(chunk, enc, cb) { cb(); } 53 }); 54 55 write._destroy = function(err, cb) { 56 assert.strictEqual(err, expected); 57 cb(err); 58 }; 59 60 const expected = new Error('kaboom'); 61 62 write.on('finish', common.mustNotCall('no finish event')); 63 write.on('close', common.mustCall()); 64 write.on('error', common.mustCall((err) => { 65 assert.strictEqual(err, expected); 66 })); 67 68 write.destroy(expected); 69 assert.strictEqual(write.destroyed, true); 70} 71 72{ 73 const write = new Writable({ 74 write(chunk, enc, cb) { cb(); }, 75 destroy: common.mustCall(function(err, cb) { 76 assert.strictEqual(err, expected); 77 cb(); 78 }) 79 }); 80 81 const expected = new Error('kaboom'); 82 83 write.on('finish', common.mustNotCall('no finish event')); 84 write.on('close', common.mustCall()); 85 86 // Error is swallowed by the custom _destroy 87 write.on('error', common.mustNotCall('no error event')); 88 89 write.destroy(expected); 90 assert.strictEqual(write.destroyed, true); 91} 92 93{ 94 const write = new Writable({ 95 write(chunk, enc, cb) { cb(); } 96 }); 97 98 write._destroy = common.mustCall(function(err, cb) { 99 assert.strictEqual(err, null); 100 cb(); 101 }); 102 103 write.destroy(); 104 assert.strictEqual(write.destroyed, true); 105} 106 107{ 108 const write = new Writable({ 109 write(chunk, enc, cb) { cb(); } 110 }); 111 112 write._destroy = common.mustCall(function(err, cb) { 113 assert.strictEqual(err, null); 114 process.nextTick(() => { 115 this.end(); 116 cb(); 117 }); 118 }); 119 120 const fail = common.mustNotCall('no finish event'); 121 122 write.on('finish', fail); 123 write.on('close', common.mustCall()); 124 125 write.destroy(); 126 127 assert.strictEqual(write.destroyed, true); 128} 129 130{ 131 const write = new Writable({ 132 write(chunk, enc, cb) { cb(); } 133 }); 134 135 const expected = new Error('kaboom'); 136 137 write._destroy = common.mustCall(function(err, cb) { 138 assert.strictEqual(err, null); 139 cb(expected); 140 }); 141 142 write.on('close', common.mustCall()); 143 write.on('finish', common.mustNotCall('no finish event')); 144 write.on('error', common.mustCall((err) => { 145 assert.strictEqual(err, expected); 146 })); 147 148 write.destroy(); 149 assert.strictEqual(write.destroyed, true); 150} 151 152{ 153 // double error case 154 const write = new Writable({ 155 write(chunk, enc, cb) { cb(); } 156 }); 157 158 let ticked = false; 159 write.on('close', common.mustCall(() => { 160 assert.strictEqual(ticked, true); 161 })); 162 write.on('error', common.mustCall((err) => { 163 assert.strictEqual(ticked, true); 164 assert.strictEqual(err.message, 'kaboom 1'); 165 assert.strictEqual(write._writableState.errorEmitted, true); 166 })); 167 168 const expected = new Error('kaboom 1'); 169 write.destroy(expected); 170 write.destroy(new Error('kaboom 2')); 171 assert.strictEqual(write._writableState.errored, expected); 172 assert.strictEqual(write._writableState.errorEmitted, false); 173 assert.strictEqual(write.destroyed, true); 174 ticked = true; 175} 176 177{ 178 const writable = new Writable({ 179 destroy: common.mustCall(function(err, cb) { 180 process.nextTick(cb, new Error('kaboom 1')); 181 }), 182 write(chunk, enc, cb) { 183 cb(); 184 } 185 }); 186 187 let ticked = false; 188 writable.on('close', common.mustCall(() => { 189 writable.on('error', common.mustNotCall()); 190 writable.destroy(new Error('hello')); 191 assert.strictEqual(ticked, true); 192 assert.strictEqual(writable._writableState.errorEmitted, true); 193 })); 194 writable.on('error', common.mustCall((err) => { 195 assert.strictEqual(ticked, true); 196 assert.strictEqual(err.message, 'kaboom 1'); 197 assert.strictEqual(writable._writableState.errorEmitted, true); 198 })); 199 200 writable.destroy(); 201 assert.strictEqual(writable.destroyed, true); 202 assert.strictEqual(writable._writableState.errored, null); 203 assert.strictEqual(writable._writableState.errorEmitted, false); 204 205 // Test case where `writable.destroy()` is called again with an error before 206 // the `_destroy()` callback is called. 207 writable.destroy(new Error('kaboom 2')); 208 assert.strictEqual(writable._writableState.errorEmitted, false); 209 assert.strictEqual(writable._writableState.errored, null); 210 211 ticked = true; 212} 213 214{ 215 const write = new Writable({ 216 write(chunk, enc, cb) { cb(); } 217 }); 218 219 write.destroyed = true; 220 assert.strictEqual(write.destroyed, true); 221 222 // The internal destroy() mechanism should not be triggered 223 write.on('close', common.mustNotCall()); 224 write.destroy(); 225} 226 227{ 228 function MyWritable() { 229 assert.strictEqual(this.destroyed, false); 230 this.destroyed = false; 231 Writable.call(this); 232 } 233 234 Object.setPrototypeOf(MyWritable.prototype, Writable.prototype); 235 Object.setPrototypeOf(MyWritable, Writable); 236 237 new MyWritable(); 238} 239 240{ 241 // Destroy and destroy callback 242 const write = new Writable({ 243 write(chunk, enc, cb) { cb(); } 244 }); 245 246 write.destroy(); 247 248 const expected = new Error('kaboom'); 249 250 write.destroy(expected, common.mustCall((err) => { 251 assert.strictEqual(err, undefined); 252 })); 253} 254 255{ 256 // Checks that `._undestroy()` restores the state so that `final` will be 257 // called again. 258 const write = new Writable({ 259 write: common.mustNotCall(), 260 final: common.mustCall((cb) => cb(), 2), 261 autoDestroy: true 262 }); 263 264 write.end(); 265 write.once('close', common.mustCall(() => { 266 write._undestroy(); 267 write.end(); 268 })); 269} 270 271{ 272 const write = new Writable(); 273 274 write.destroy(); 275 write.on('error', common.mustNotCall()); 276 write.write('asd', common.expectsError({ 277 name: 'Error', 278 code: 'ERR_STREAM_DESTROYED', 279 message: 'Cannot call write after a stream was destroyed' 280 })); 281} 282 283{ 284 const write = new Writable({ 285 write(chunk, enc, cb) { cb(); } 286 }); 287 288 write.on('error', common.mustNotCall()); 289 290 write.cork(); 291 write.write('asd', common.mustCall()); 292 write.uncork(); 293 294 write.cork(); 295 write.write('asd', common.expectsError({ 296 name: 'Error', 297 code: 'ERR_STREAM_DESTROYED', 298 message: 'Cannot call write after a stream was destroyed' 299 })); 300 write.destroy(); 301 write.write('asd', common.expectsError({ 302 name: 'Error', 303 code: 'ERR_STREAM_DESTROYED', 304 message: 'Cannot call write after a stream was destroyed' 305 })); 306 write.uncork(); 307} 308 309{ 310 // Call end(cb) after error & destroy 311 312 const write = new Writable({ 313 write(chunk, enc, cb) { cb(new Error('asd')); } 314 }); 315 write.on('error', common.mustCall(() => { 316 write.destroy(); 317 let ticked = false; 318 write.end(common.mustCall((err) => { 319 assert.strictEqual(ticked, true); 320 assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); 321 })); 322 ticked = true; 323 })); 324 write.write('asd'); 325} 326 327{ 328 // Call end(cb) after finish & destroy 329 330 const write = new Writable({ 331 write(chunk, enc, cb) { cb(); } 332 }); 333 write.on('finish', common.mustCall(() => { 334 write.destroy(); 335 let ticked = false; 336 write.end(common.mustCall((err) => { 337 assert.strictEqual(ticked, true); 338 assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED'); 339 })); 340 ticked = true; 341 })); 342 write.end(); 343} 344 345{ 346 // Call end(cb) after error & destroy and don't trigger 347 // unhandled exception. 348 349 const write = new Writable({ 350 write(chunk, enc, cb) { process.nextTick(cb); } 351 }); 352 const _err = new Error('asd'); 353 write.once('error', common.mustCall((err) => { 354 assert.strictEqual(err.message, 'asd'); 355 })); 356 write.end('asd', common.mustCall((err) => { 357 assert.strictEqual(err, _err); 358 })); 359 write.destroy(_err); 360} 361 362{ 363 // Call buffered write callback with error 364 365 const _err = new Error('asd'); 366 const write = new Writable({ 367 write(chunk, enc, cb) { 368 process.nextTick(cb, _err); 369 }, 370 autoDestroy: false 371 }); 372 write.cork(); 373 write.write('asd', common.mustCall((err) => { 374 assert.strictEqual(err, _err); 375 })); 376 write.write('asd', common.mustCall((err) => { 377 assert.strictEqual(err, _err); 378 })); 379 write.on('error', common.mustCall((err) => { 380 assert.strictEqual(err, _err); 381 })); 382 write.uncork(); 383} 384 385{ 386 // Ensure callback order. 387 388 let state = 0; 389 const write = new Writable({ 390 write(chunk, enc, cb) { 391 // `setImmediate()` is used on purpose to ensure the callback is called 392 // after `process.nextTick()` callbacks. 393 setImmediate(cb); 394 } 395 }); 396 write.write('asd', common.mustCall(() => { 397 assert.strictEqual(state++, 0); 398 })); 399 write.write('asd', common.mustCall((err) => { 400 assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); 401 assert.strictEqual(state++, 1); 402 })); 403 write.destroy(); 404} 405 406{ 407 const write = new Writable({ 408 autoDestroy: false, 409 write(chunk, enc, cb) { 410 cb(); 411 cb(); 412 } 413 }); 414 415 write.on('error', common.mustCall(() => { 416 assert(write._writableState.errored); 417 })); 418 write.write('asd'); 419} 420 421{ 422 const ac = new AbortController(); 423 const write = addAbortSignal(ac.signal, new Writable({ 424 write(chunk, enc, cb) { cb(); } 425 })); 426 427 write.on('error', common.mustCall((e) => { 428 assert.strictEqual(e.name, 'AbortError'); 429 assert.strictEqual(write.destroyed, true); 430 })); 431 write.write('asd'); 432 ac.abort(); 433} 434 435{ 436 const ac = new AbortController(); 437 const write = new Writable({ 438 signal: ac.signal, 439 write(chunk, enc, cb) { cb(); } 440 }); 441 442 write.on('error', common.mustCall((e) => { 443 assert.strictEqual(e.name, 'AbortError'); 444 assert.strictEqual(write.destroyed, true); 445 })); 446 write.write('asd'); 447 ac.abort(); 448} 449 450{ 451 const signal = AbortSignal.abort(); 452 453 const write = new Writable({ 454 signal, 455 write(chunk, enc, cb) { cb(); } 456 }); 457 458 write.on('error', common.mustCall((e) => { 459 assert.strictEqual(e.name, 'AbortError'); 460 assert.strictEqual(write.destroyed, true); 461 })); 462} 463 464{ 465 // Destroy twice 466 const write = new Writable({ 467 write(chunk, enc, cb) { cb(); } 468 }); 469 470 write.end(common.mustCall()); 471 write.destroy(); 472 write.destroy(); 473} 474 475{ 476 // https://github.com/nodejs/node/issues/39356 477 const s = new Writable({ 478 final() {} 479 }); 480 const _err = new Error('oh no'); 481 // Remove `callback` and it works 482 s.end(common.mustCall((err) => { 483 assert.strictEqual(err, _err); 484 })); 485 s.on('error', common.mustCall((err) => { 486 assert.strictEqual(err, _err); 487 })); 488 s.destroy(_err); 489} 490