'use strict'; var common = require('../common'); var W = require('_stream_writable'); var D = require('_stream_duplex'); var assert = require('assert'); var util = require('util'); util.inherits(TestWriter, W); function TestWriter() { W.apply(this, arguments); this.buffer = []; this.written = 0; } TestWriter.prototype._write = function(chunk, encoding, cb) { // simulate a small unpredictable latency setTimeout(function() { this.buffer.push(chunk.toString()); this.written += chunk.length; cb(); }.bind(this), Math.floor(Math.random() * 10)); }; var chunks = new Array(50); for (var i = 0; i < chunks.length; i++) { chunks[i] = new Array(i + 1).join('x'); } // tiny node-tap lookalike. var tests = []; var count = 0; function test(name, fn) { count++; tests.push([name, fn]); } function run() { var next = tests.shift(); if (!next) return console.error('ok'); var name = next[0]; var fn = next[1]; console.log('# %s', name); fn({ same: assert.deepEqual, equal: assert.equal, end: function() { count--; run(); } }); } // ensure all tests have run process.on('exit', function() { assert.equal(count, 0); }); process.nextTick(run); test('write fast', function(t) { var tw = new TestWriter({ highWaterMark: 100 }); tw.on('finish', function() { t.same(tw.buffer, chunks, 'got chunks in the right order'); t.end(); }); chunks.forEach(function(chunk) { // screw backpressure. Just buffer it all up. tw.write(chunk); }); tw.end(); }); test('write slow', function(t) { var tw = new TestWriter({ highWaterMark: 100 }); tw.on('finish', function() { t.same(tw.buffer, chunks, 'got chunks in the right order'); t.end(); }); var i = 0; (function W() { tw.write(chunks[i++]); if (i < chunks.length) setTimeout(W, 10); else tw.end(); })(); }); test('write backpressure', function(t) { var tw = new TestWriter({ highWaterMark: 50 }); var drains = 0; tw.on('finish', function() { t.same(tw.buffer, chunks, 'got chunks in the right order'); t.equal(drains, 17); t.end(); }); tw.on('drain', function() { drains++; }); var i = 0; (function W() { do { var ret = tw.write(chunks[i++]); } while (ret !== false && i < chunks.length); if (i < chunks.length) { assert(tw._writableState.length >= 50); tw.once('drain', W); } else { tw.end(); } })(); }); test('write bufferize', function(t) { var tw = new TestWriter({ highWaterMark: 100 }); var encodings = [ 'hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', undefined ]; tw.on('finish', function() { t.same(tw.buffer, chunks, 'got the expected chunks'); }); chunks.forEach(function(chunk, i) { var enc = encodings[ i % encodings.length ]; chunk = new Buffer(chunk); tw.write(chunk.toString(enc), enc); }); t.end(); }); test('write no bufferize', function(t) { var tw = new TestWriter({ highWaterMark: 100, decodeStrings: false }); tw._write = function(chunk, encoding, cb) { assert(typeof chunk === 'string'); chunk = new Buffer(chunk, encoding); return TestWriter.prototype._write.call(this, chunk, encoding, cb); }; var encodings = [ 'hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', undefined ]; tw.on('finish', function() { t.same(tw.buffer, chunks, 'got the expected chunks'); }); chunks.forEach(function(chunk, i) { var enc = encodings[ i % encodings.length ]; chunk = new Buffer(chunk); tw.write(chunk.toString(enc), enc); }); t.end(); }); test('write callbacks', function(t) { var callbacks = chunks.map(function(chunk, i) { return [i, function(er) { callbacks._called[i] = chunk; }]; }).reduce(function(set, x) { set['callback-' + x[0]] = x[1]; return set; }, {}); callbacks._called = []; var tw = new TestWriter({ highWaterMark: 100 }); tw.on('finish', function() { process.nextTick(function() { t.same(tw.buffer, chunks, 'got chunks in the right order'); t.same(callbacks._called, chunks, 'called all callbacks'); t.end(); }); }); chunks.forEach(function(chunk, i) { tw.write(chunk, callbacks['callback-' + i]); }); tw.end(); }); test('end callback', function(t) { var tw = new TestWriter(); tw.end(function() { t.end(); }); }); test('end callback with chunk', function(t) { var tw = new TestWriter(); tw.end(new Buffer('hello world'), function() { t.end(); }); }); test('end callback with chunk and encoding', function(t) { var tw = new TestWriter(); tw.end('hello world', 'ascii', function() { t.end(); }); }); test('end callback after .write() call', function(t) { var tw = new TestWriter(); tw.write(new Buffer('hello world')); tw.end(function() { t.end(); }); }); test('end callback called after write callback', function(t) { var tw = new TestWriter(); var writeCalledback = false; tw.write(new Buffer('hello world'), function() { writeCalledback = true; }); tw.end(function() { t.equal(writeCalledback, true); t.end(); }); }); test('encoding should be ignored for buffers', function(t) { var tw = new W(); var hex = '018b5e9a8f6236ffe30e31baf80d2cf6eb'; tw._write = function(chunk, encoding, cb) { t.equal(chunk.toString('hex'), hex); t.end(); }; var buf = new Buffer(hex, 'hex'); tw.write(buf, 'binary'); }); test('writables are not pipable', function(t) { var w = new W(); w._write = function() {}; var gotError = false; w.on('error', function(er) { gotError = true; }); w.pipe(process.stdout); assert(gotError); t.end(); }); test('duplexes are pipable', function(t) { var d = new D(); d._read = function() {}; d._write = function() {}; var gotError = false; d.on('error', function(er) { gotError = true; }); d.pipe(process.stdout); assert(!gotError); t.end(); }); test('end(chunk) two times is an error', function(t) { var w = new W(); w._write = function() {}; var gotError = false; w.on('error', function(er) { gotError = true; t.equal(er.message, 'write after end'); }); w.end('this is the end'); w.end('and so is this'); process.nextTick(function() { assert(gotError); t.end(); }); }); test('dont end while writing', function(t) { var w = new W(); var wrote = false; w._write = function(chunk, e, cb) { assert(!this.writing); wrote = true; this.writing = true; setTimeout(function() { this.writing = false; cb(); }); }; w.on('finish', function() { assert(wrote); t.end(); }); w.write(Buffer(0)); w.end(); }); test('finish does not come before write cb', function(t) { var w = new W(); var writeCb = false; w._write = function(chunk, e, cb) { setTimeout(function() { writeCb = true; cb(); }, 10); }; w.on('finish', function() { assert(writeCb); t.end(); }); w.write(Buffer(0)); w.end(); }); test('finish does not come before sync _write cb', function(t) { var w = new W(); var writeCb = false; w._write = function(chunk, e, cb) { cb(); }; w.on('finish', function() { assert(writeCb); t.end(); }); w.write(Buffer(0), function(er) { writeCb = true; }); w.end(); }); test('finish is emitted if last chunk is empty', function(t) { var w = new W(); w._write = function(chunk, e, cb) { process.nextTick(cb); }; w.on('finish', function() { t.end(); }); w.write(Buffer(1)); w.end(Buffer(0)); });