1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24const assert = require('assert'); 25const PassThrough = require('_stream_passthrough'); 26const Transform = require('_stream_transform'); 27 28{ 29 // Verify writable side consumption 30 const tx = new Transform({ 31 highWaterMark: 10 32 }); 33 34 let transformed = 0; 35 tx._transform = function(chunk, encoding, cb) { 36 transformed += chunk.length; 37 tx.push(chunk); 38 cb(); 39 }; 40 41 for (let i = 1; i <= 10; i++) { 42 tx.write(Buffer.allocUnsafe(i)); 43 } 44 tx.end(); 45 46 assert.strictEqual(tx.readableLength, 10); 47 assert.strictEqual(transformed, 10); 48 assert.strictEqual(tx._transformState.writechunk.length, 5); 49 assert.deepStrictEqual(tx.writableBuffer.map(function(c) { 50 return c.chunk.length; 51 }), [6, 7, 8, 9, 10]); 52} 53 54{ 55 // Verify passthrough behavior 56 const pt = new PassThrough(); 57 58 pt.write(Buffer.from('foog')); 59 pt.write(Buffer.from('bark')); 60 pt.write(Buffer.from('bazy')); 61 pt.write(Buffer.from('kuel')); 62 pt.end(); 63 64 assert.strictEqual(pt.read(5).toString(), 'foogb'); 65 assert.strictEqual(pt.read(5).toString(), 'arkba'); 66 assert.strictEqual(pt.read(5).toString(), 'zykue'); 67 assert.strictEqual(pt.read(5).toString(), 'l'); 68} 69 70{ 71 // Verify object passthrough behavior 72 const pt = new PassThrough({ objectMode: true }); 73 74 pt.write(1); 75 pt.write(true); 76 pt.write(false); 77 pt.write(0); 78 pt.write('foo'); 79 pt.write(''); 80 pt.write({ a: 'b' }); 81 pt.end(); 82 83 assert.strictEqual(pt.read(), 1); 84 assert.strictEqual(pt.read(), true); 85 assert.strictEqual(pt.read(), false); 86 assert.strictEqual(pt.read(), 0); 87 assert.strictEqual(pt.read(), 'foo'); 88 assert.strictEqual(pt.read(), ''); 89 assert.deepStrictEqual(pt.read(), { a: 'b' }); 90} 91 92{ 93 // Verify passthrough constructor behavior 94 const pt = PassThrough(); 95 96 assert(pt instanceof PassThrough); 97} 98 99{ 100 // Verify transform constructor behavior 101 const pt = Transform(); 102 103 assert(pt instanceof Transform); 104} 105 106{ 107 // Perform a simple transform 108 const pt = new Transform(); 109 pt._transform = function(c, e, cb) { 110 const ret = Buffer.alloc(c.length, 'x'); 111 pt.push(ret); 112 cb(); 113 }; 114 115 pt.write(Buffer.from('foog')); 116 pt.write(Buffer.from('bark')); 117 pt.write(Buffer.from('bazy')); 118 pt.write(Buffer.from('kuel')); 119 pt.end(); 120 121 assert.strictEqual(pt.read(5).toString(), 'xxxxx'); 122 assert.strictEqual(pt.read(5).toString(), 'xxxxx'); 123 assert.strictEqual(pt.read(5).toString(), 'xxxxx'); 124 assert.strictEqual(pt.read(5).toString(), 'x'); 125} 126 127{ 128 // Verify simple object transform 129 const pt = new Transform({ objectMode: true }); 130 pt._transform = function(c, e, cb) { 131 pt.push(JSON.stringify(c)); 132 cb(); 133 }; 134 135 pt.write(1); 136 pt.write(true); 137 pt.write(false); 138 pt.write(0); 139 pt.write('foo'); 140 pt.write(''); 141 pt.write({ a: 'b' }); 142 pt.end(); 143 144 assert.strictEqual(pt.read(), '1'); 145 assert.strictEqual(pt.read(), 'true'); 146 assert.strictEqual(pt.read(), 'false'); 147 assert.strictEqual(pt.read(), '0'); 148 assert.strictEqual(pt.read(), '"foo"'); 149 assert.strictEqual(pt.read(), '""'); 150 assert.strictEqual(pt.read(), '{"a":"b"}'); 151} 152 153{ 154 // Verify async passthrough 155 const pt = new Transform(); 156 pt._transform = function(chunk, encoding, cb) { 157 setTimeout(function() { 158 pt.push(chunk); 159 cb(); 160 }, 10); 161 }; 162 163 pt.write(Buffer.from('foog')); 164 pt.write(Buffer.from('bark')); 165 pt.write(Buffer.from('bazy')); 166 pt.write(Buffer.from('kuel')); 167 pt.end(); 168 169 pt.on('finish', common.mustCall(function() { 170 assert.strictEqual(pt.read(5).toString(), 'foogb'); 171 assert.strictEqual(pt.read(5).toString(), 'arkba'); 172 assert.strictEqual(pt.read(5).toString(), 'zykue'); 173 assert.strictEqual(pt.read(5).toString(), 'l'); 174 })); 175} 176 177{ 178 // Verify asymmetric transform (expand) 179 const pt = new Transform(); 180 181 // Emit each chunk 2 times. 182 pt._transform = function(chunk, encoding, cb) { 183 setTimeout(function() { 184 pt.push(chunk); 185 setTimeout(function() { 186 pt.push(chunk); 187 cb(); 188 }, 10); 189 }, 10); 190 }; 191 192 pt.write(Buffer.from('foog')); 193 pt.write(Buffer.from('bark')); 194 pt.write(Buffer.from('bazy')); 195 pt.write(Buffer.from('kuel')); 196 pt.end(); 197 198 pt.on('finish', common.mustCall(function() { 199 assert.strictEqual(pt.read(5).toString(), 'foogf'); 200 assert.strictEqual(pt.read(5).toString(), 'oogba'); 201 assert.strictEqual(pt.read(5).toString(), 'rkbar'); 202 assert.strictEqual(pt.read(5).toString(), 'kbazy'); 203 assert.strictEqual(pt.read(5).toString(), 'bazyk'); 204 assert.strictEqual(pt.read(5).toString(), 'uelku'); 205 assert.strictEqual(pt.read(5).toString(), 'el'); 206 })); 207} 208 209{ 210 // Verify asymmetric transform (compress) 211 const pt = new Transform(); 212 213 // Each output is the first char of 3 consecutive chunks, 214 // or whatever's left. 215 pt.state = ''; 216 217 pt._transform = function(chunk, encoding, cb) { 218 if (!chunk) 219 chunk = ''; 220 const s = chunk.toString(); 221 setTimeout(() => { 222 this.state += s.charAt(0); 223 if (this.state.length === 3) { 224 pt.push(Buffer.from(this.state)); 225 this.state = ''; 226 } 227 cb(); 228 }, 10); 229 }; 230 231 pt._flush = function(cb) { 232 // Just output whatever we have. 233 pt.push(Buffer.from(this.state)); 234 this.state = ''; 235 cb(); 236 }; 237 238 pt.write(Buffer.from('aaaa')); 239 pt.write(Buffer.from('bbbb')); 240 pt.write(Buffer.from('cccc')); 241 pt.write(Buffer.from('dddd')); 242 pt.write(Buffer.from('eeee')); 243 pt.write(Buffer.from('aaaa')); 244 pt.write(Buffer.from('bbbb')); 245 pt.write(Buffer.from('cccc')); 246 pt.write(Buffer.from('dddd')); 247 pt.write(Buffer.from('eeee')); 248 pt.write(Buffer.from('aaaa')); 249 pt.write(Buffer.from('bbbb')); 250 pt.write(Buffer.from('cccc')); 251 pt.write(Buffer.from('dddd')); 252 pt.end(); 253 254 // 'abcdeabcdeabcd' 255 pt.on('finish', common.mustCall(function() { 256 assert.strictEqual(pt.read(5).toString(), 'abcde'); 257 assert.strictEqual(pt.read(5).toString(), 'abcde'); 258 assert.strictEqual(pt.read(5).toString(), 'abcd'); 259 })); 260} 261 262// This tests for a stall when data is written to a full stream 263// that has empty transforms. 264{ 265 // Verify complex transform behavior 266 let count = 0; 267 let saved = null; 268 const pt = new Transform({ highWaterMark: 3 }); 269 pt._transform = function(c, e, cb) { 270 if (count++ === 1) 271 saved = c; 272 else { 273 if (saved) { 274 pt.push(saved); 275 saved = null; 276 } 277 pt.push(c); 278 } 279 280 cb(); 281 }; 282 283 pt.once('readable', function() { 284 process.nextTick(function() { 285 pt.write(Buffer.from('d')); 286 pt.write(Buffer.from('ef'), common.mustCall(function() { 287 pt.end(); 288 })); 289 assert.strictEqual(pt.read().toString(), 'abcdef'); 290 assert.strictEqual(pt.read(), null); 291 }); 292 }); 293 294 pt.write(Buffer.from('abc')); 295} 296 297 298{ 299 // Verify passthrough event emission 300 const pt = new PassThrough(); 301 let emits = 0; 302 pt.on('readable', function() { 303 emits++; 304 }); 305 306 pt.write(Buffer.from('foog')); 307 pt.write(Buffer.from('bark')); 308 309 assert.strictEqual(emits, 0); 310 assert.strictEqual(pt.read(5).toString(), 'foogb'); 311 assert.strictEqual(String(pt.read(5)), 'null'); 312 assert.strictEqual(emits, 0); 313 314 pt.write(Buffer.from('bazy')); 315 pt.write(Buffer.from('kuel')); 316 317 assert.strictEqual(emits, 0); 318 assert.strictEqual(pt.read(5).toString(), 'arkba'); 319 assert.strictEqual(pt.read(5).toString(), 'zykue'); 320 assert.strictEqual(pt.read(5), null); 321 322 pt.end(); 323 324 assert.strictEqual(emits, 1); 325 assert.strictEqual(pt.read(5).toString(), 'l'); 326 assert.strictEqual(pt.read(5), null); 327 assert.strictEqual(emits, 1); 328} 329 330{ 331 // Verify passthrough event emission reordering 332 const pt = new PassThrough(); 333 let emits = 0; 334 pt.on('readable', function() { 335 emits++; 336 }); 337 338 pt.write(Buffer.from('foog')); 339 pt.write(Buffer.from('bark')); 340 341 assert.strictEqual(emits, 0); 342 assert.strictEqual(pt.read(5).toString(), 'foogb'); 343 assert.strictEqual(pt.read(5), null); 344 345 pt.once('readable', common.mustCall(function() { 346 assert.strictEqual(pt.read(5).toString(), 'arkba'); 347 assert.strictEqual(pt.read(5), null); 348 349 pt.once('readable', common.mustCall(function() { 350 assert.strictEqual(pt.read(5).toString(), 'zykue'); 351 assert.strictEqual(pt.read(5), null); 352 pt.once('readable', common.mustCall(function() { 353 assert.strictEqual(pt.read(5).toString(), 'l'); 354 assert.strictEqual(pt.read(5), null); 355 assert.strictEqual(emits, 3); 356 })); 357 pt.end(); 358 })); 359 pt.write(Buffer.from('kuel')); 360 })); 361 362 pt.write(Buffer.from('bazy')); 363} 364 365{ 366 // Verify passthrough facade 367 const pt = new PassThrough(); 368 const datas = []; 369 pt.on('data', function(chunk) { 370 datas.push(chunk.toString()); 371 }); 372 373 pt.on('end', common.mustCall(function() { 374 assert.deepStrictEqual(datas, ['foog', 'bark', 'bazy', 'kuel']); 375 })); 376 377 pt.write(Buffer.from('foog')); 378 setTimeout(function() { 379 pt.write(Buffer.from('bark')); 380 setTimeout(function() { 381 pt.write(Buffer.from('bazy')); 382 setTimeout(function() { 383 pt.write(Buffer.from('kuel')); 384 setTimeout(function() { 385 pt.end(); 386 }, 10); 387 }, 10); 388 }, 10); 389 }, 10); 390} 391 392{ 393 // Verify object transform (JSON parse) 394 const jp = new Transform({ objectMode: true }); 395 jp._transform = function(data, encoding, cb) { 396 try { 397 jp.push(JSON.parse(data)); 398 cb(); 399 } catch (er) { 400 cb(er); 401 } 402 }; 403 404 // Anything except null/undefined is fine. 405 // those are "magic" in the stream API, because they signal EOF. 406 const objects = [ 407 { foo: 'bar' }, 408 100, 409 'string', 410 { nested: { things: [ { foo: 'bar' }, 100, 'string' ] } }, 411 ]; 412 413 let ended = false; 414 jp.on('end', function() { 415 ended = true; 416 }); 417 418 objects.forEach(function(obj) { 419 jp.write(JSON.stringify(obj)); 420 const res = jp.read(); 421 assert.deepStrictEqual(res, obj); 422 }); 423 424 jp.end(); 425 // Read one more time to get the 'end' event 426 jp.read(); 427 428 process.nextTick(common.mustCall(function() { 429 assert.strictEqual(ended, true); 430 })); 431} 432 433{ 434 // Verify object transform (JSON stringify) 435 const js = new Transform({ objectMode: true }); 436 js._transform = function(data, encoding, cb) { 437 try { 438 js.push(JSON.stringify(data)); 439 cb(); 440 } catch (er) { 441 cb(er); 442 } 443 }; 444 445 // Anything except null/undefined is fine. 446 // those are "magic" in the stream API, because they signal EOF. 447 const objects = [ 448 { foo: 'bar' }, 449 100, 450 'string', 451 { nested: { things: [ { foo: 'bar' }, 100, 'string' ] } }, 452 ]; 453 454 let ended = false; 455 js.on('end', function() { 456 ended = true; 457 }); 458 459 objects.forEach(function(obj) { 460 js.write(obj); 461 const res = js.read(); 462 assert.strictEqual(res, JSON.stringify(obj)); 463 }); 464 465 js.end(); 466 // Read one more time to get the 'end' event 467 js.read(); 468 469 process.nextTick(common.mustCall(function() { 470 assert.strictEqual(ended, true); 471 })); 472} 473