UNPKG

pipette

Version:

Stream and pipe utilities for Node

600 lines (491 loc) 15.7 kB
// Copyright 2012 The Obvious Corporation. /* * Modules used */ "use strict"; var assert = require("assert"); var events = require("events"); var Blip = require("../").Blip; var Dropper = require("../").Dropper; var EventCollector = require("./eventcoll").EventCollector; var emit = require("./emit").emit; /* * Helper functions */ /** * Combines two buffers. */ function addBufs(buf1, buf2) { if (!buf1 || (buf1.length === 0)) { return buf2; } var length = buf1.length + buf2.length; var buf = new Buffer(length); buf1.copy(buf); buf2.copy(buf, buf1.length); return buf; } /* * Tests */ /** * Makes sure the constructor doesn't fail off the bat. */ function constructor() { new Dropper(new events.EventEmitter()); new Dropper(new events.EventEmitter(), { size: 10 }); new Dropper(new events.EventEmitter(), { allowMultiple: true }); new Dropper(new events.EventEmitter(), { allowMultiple: false }); new Dropper(new events.EventEmitter(), { ifPartial: "emit" }); new Dropper(new events.EventEmitter(), { ifPartial: "error" }); new Dropper(new events.EventEmitter(), { ifPartial: "ignore" }); new Dropper(new events.EventEmitter(), { ifPartial: "pad" }); new Dropper(new events.EventEmitter(), { size: 20, allowMultiple: true, ifPartial: "emit" }); } /** * Tests expected constructor failures. */ function constructorFailure() { function f1() { new Dropper(undefined); } assert.throws(f1, /Missing source/); function f2() { new Dropper(["hello"]); } assert.throws(f2, /Source not an EventEmitter/); // This is an already-ended Stream-per-se. var bad = new Blip(); bad.resume(); function f3() { new Dropper(bad); } assert.throws(f3, /Source already ended./); function f4() { new Dropper(new events.EventEmitter(), { size: -1 }); } assert.throws(f4, /Bad value for option: size/); function f5() { new Dropper(new events.EventEmitter(), { size: 0 }); } assert.throws(f5, /Bad value for option: size/); function f6() { new Dropper(new events.EventEmitter(), { size: "yo" }); } assert.throws(f6, /Bad value for option: size/); function f7() { new Dropper(new events.EventEmitter(), { allowMultiple: "hey" }); } assert.throws(f7, /Bad value for option: allowMultiple/); function f8() { new Dropper(new events.EventEmitter(), { ifPartial: "blort" }); } assert.throws(f8, /Bad value for option: ifPartial/); function f9() { new Dropper(new events.EventEmitter(), { encoding: "blort" }); } assert.throws(f9, /Bad value for option: encoding/); function f10() { new Dropper(new events.EventEmitter(), { incomingEncoding: "blort" }); } assert.throws(f10, /Bad value for option: incomingEncoding/); function f11() { new Dropper(new events.EventEmitter(), { paused: "blort" }); } assert.throws(f11, /Bad value for option: paused/); function f12() { new Dropper(new events.EventEmitter(), { notARealOption: "yo" }); } assert.throws(f12, /Unknown option: notARealOption/); } /** * Tests that `readable` is true until an end-type event comes through. */ function readableTransition() { tryWith("end"); tryWith("close"); tryWith("error", new Error("criminy")); function tryWith(name, arg) { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 10 }); var coll = new EventCollector(); dropper.pause(); coll.listenAllCommon(dropper); assert.ok(dropper.readable); dropper.resume(); assert.ok(dropper.readable); dropper.pause(); assert.ok(dropper.readable); emit(source, name, arg); assert.ok(dropper.readable); assert.equal(coll.events.length, 0); dropper.resume(); assert.equal(coll.events.length, 2); assert.ok(!dropper.readable); } } /** * Tests that no events will get passed through after a close sequence * (`end` or `error` followed by `close`). */ function eventsAfterClose() { var theError = new Error("insufficient muffins"); tryWith(false, "data", "stuff"); tryWith(false, "end"); tryWith(false, "close"); tryWith(false, "error", new Error("oy")); tryWith(true, "data", "stuff"); tryWith(true, "end"); tryWith(true, "close"); tryWith(true, "error", new Error("oy")); function tryWith(doError, name, arg) { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 10 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); if (doError) { source.emit("error", theError); } else { source.emit("end"); } assert.equal(coll.events.length, 2); if (doError) { coll.assertEvent(0, dropper, "error", [theError]); } else { coll.assertEvent(0, dropper, "end"); } coll.assertEvent(1, dropper, "close"); coll.reset(); // In case the event to be sent is an `error`, this listener // suppresses the Node default "unhandled error" behavior. source.on("error", function () { /*ignore*/ }); emit(source, name, arg); assert.equal(coll.events.length, 0); } } /** * Tests basic non-multiple event sequence, using various block sizes. */ function nonMultipleEventSequence() { var rawData = new Buffer(50000); rawData[0] = 0; rawData[1] = 1; for (var i = 2; i < rawData.length; i++) { rawData[i] = (i + rawData[i - 2] + rawData[i - 1]) & 0xff; } for (var i = 1; i < 500; i += 27) { tryWith(i); } function tryWith(blockSize) { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: blockSize }); var coll = new EventCollector(); var data = rawData; var pending = undefined; // expected pending data var nextLength = 1; coll.listenAllCommon(dropper); while (data.length !== 0) { if (nextLength > data.length) { nextLength = data.length; } var emitData = data.slice(0, nextLength); data = data.slice(nextLength); pending = addBufs(pending, emitData); var expectedEventCount = Math.floor(pending.length / blockSize); source.emit("data", emitData); assert.equal(coll.events.length, expectedEventCount); for (var i = 0; i < expectedEventCount; i++) { var expectData = pending.slice(0, blockSize); pending = pending.slice(blockSize); coll.assertEvent(i, dropper, "data", [expectData]); } coll.reset(); } source.emit("end"); if (pending.length === 0) { assert.equal(coll.events.length, 2); coll.assertEvent(0, dropper, "end"); coll.assertEvent(1, dropper, "close"); } else { assert.equal(coll.events.length, 3); coll.assertEvent(0, dropper, "data", [pending]); coll.assertEvent(1, dropper, "end"); coll.assertEvent(2, dropper, "close"); } } } /** * Tests basic multiple-okay event sequence, using various block sizes. */ function multipleOkayEventSequence() { var rawData = new Buffer(50000); rawData[0] = 10; rawData[1] = 12; for (var i = 2; i < rawData.length; i++) { rawData[i] = (i + rawData[i - 2] + rawData[i - 1]) & 0xff; } for (var i = 1; i < 500; i += 27) { tryWith(i); } function tryWith(blockSize) { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: blockSize }); var coll = new EventCollector(); var data = rawData; var pending = undefined; // expected pending data var nextLength = 1; coll.listenAllCommon(dropper); while (data.length !== 0) { if (nextLength > data.length) { nextLength = data.length; } var emitData = data.slice(0, nextLength); data = data.slice(nextLength); pending = addBufs(pending, emitData); var expectEvent = (pending.length >= blockSize); source.emit("data", emitData); assert.equal(coll.events.length, expectEvent ? 1 : 0); if (expectEvent) { var expectLength = (pending.length - pending.length % blockSize); var expectData = pending.slice(0, expectLength); pending = pending.slice(expectLength); coll.assertEvent(0, dropper, "data", [expectData]); coll.reset(); } } source.emit("end"); if (pending.length === 0) { assert.equal(coll.events.length, 2); coll.assertEvent(0, dropper, "end"); coll.assertEvent(1, dropper, "close"); } else { assert.equal(coll.events.length, 3); coll.assertEvent(0, dropper, "data", [pending]); coll.assertEvent(1, dropper, "end"); coll.assertEvent(2, dropper, "close"); } } } /** * Tests the basic error event sequence. */ function errorSequence() { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 25 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); source.emit("data", "Muffins are a perfect source of nutritive value."); source.emit("error", new Error("oy")); assert.equal(coll.events.length, 4); coll.assertEvent(0, dropper, "data", [new Buffer("Muffins are a perfect sou")]); coll.assertEvent(1, dropper, "data", [new Buffer("rce of nutritive value.")]); coll.assertEvent(2, dropper, "error", [new Error("oy")]); coll.assertEvent(3, dropper, "close"); } /** * Tests a buffered (because of `pause()`) event sequence. */ function bufferedSequence() { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 20 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); dropper.pause(); source.emit("data", "This "); source.emit("data", "is "); source.emit("data", "a "); source.emit("data", "test "); source.emit("data", "of "); source.emit("data", "the "); source.emit("data", "emergency "); source.emit("data", "broadcast "); source.emit("data", "system."); source.emit("end"); assert.ok(dropper.readable); dropper.resume(); assert.ok(!dropper.readable); assert.equal(coll.events.length, 5); coll.assertEvent(0, dropper, "data", [new Buffer("This is a test of th")]); coll.assertEvent(1, dropper, "data", [new Buffer("e emergency broadcas")]); coll.assertEvent(2, dropper, "data", [new Buffer("t system.")]); coll.assertEvent(3, dropper, "end"); coll.assertEvent(4, dropper, "close"); } /** * Tests the various `ifPartial` values. */ function ifPartial() { var theData = new Buffer("yummy"); var source = new events.EventEmitter(); var coll = new EventCollector(); var dropper; tryWith("emit"); assert.equal(coll.events.length, 3); coll.assertEvent(0, dropper, "data", [theData]); coll.assertEvent(1, dropper, "end"); coll.assertEvent(2, dropper, "close"); tryWith("error"); assert.equal(coll.events.length, 2); coll.assertEvent(0, dropper, "error", [new Error("Partial buffer at end.")]); coll.assertEvent(1, dropper, "close"); tryWith("ignore"); coll.assertEvent(0, dropper, "end"); coll.assertEvent(1, dropper, "close"); tryWith("pad"); assert.equal(coll.events.length, 3); coll.assertEvent(0, dropper, "data", [new Buffer("yummy\0\0\0\0\0")]); coll.assertEvent(1, dropper, "end"); coll.assertEvent(2, dropper, "close"); function tryWith(partial) { dropper = new Dropper(source, { size: 10, ifPartial: partial }); coll.reset(); coll.listenAllCommon(dropper); source.emit("data", theData); source.emit("close"); } } /** * Tests that `setEncoding()` operates as expected in terms of baseline * functionality. */ function setEncoding() { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 6 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); tryWith(undefined); tryWith("ascii"); tryWith("base64"); tryWith("hex"); tryWith("utf8"); function tryWith(name) { var origData = new Buffer("muffintastic"); var expectData1 = origData.slice(0, 6); var expectData2 = origData.slice(6); if (name) { expectData1 = expectData1.toString(name); expectData2 = expectData2.toString(name); } dropper.setEncoding(name); source.emit("data", origData); assert.equal(coll.events.length, 2); coll.assertEvent(0, dropper, "data", [expectData1]); coll.assertEvent(1, dropper, "data", [expectData2]); coll.reset(); } } /** * Tests that `setIncomingEncoding()` operates as expected in terms of * baseline functionality. */ function setIncomingEncoding() { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 35 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); tryWith(undefined); tryWith("ascii"); tryWith("base64"); tryWith("hex"); tryWith("utf8"); assert.equal(coll.events.length, 1); coll.assertEvent(0, dropper, "data", [new Buffer("biscuitbiscuitbiscuitbiscuitbiscuit")]); function tryWith(name) { var origData = new Buffer("biscuit"); var emitData; if (name) { emitData = origData.toString(name); } else { emitData = origData; } dropper.setIncomingEncoding(name); source.emit("data", emitData); } } /** * Tests the common constructor options. */ function commonOptions() { var theData = new Buffer("scone"); var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 5, encoding: "hex", incomingEncoding: "base64", paused: true }); var coll = new EventCollector(); coll.listenAllCommon(dropper); source.emit("data", theData.toString("base64")); source.emit("end"); source.emit("close"); assert.ok(dropper.readable); assert.equal(coll.events.length, 0); dropper.resume(); assert.ok(!dropper.readable); assert.equal(coll.events.length, 3); coll.assertEvent(0, dropper, "data", [theData.toString("hex")]); coll.assertEvent(1, dropper, "end"); coll.assertEvent(2, dropper, "close"); } /** * Ensure that no events get passed after a `destroy()` call. Also, * proves that the dropper isn't even listening for events from the * source anymore. */ function afterDestroy() { var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 100 }); var coll = new EventCollector(); coll.listenAllCommon(dropper); dropper.destroy(); source.emit("data", "yes?"); source.emit("end"); source.emit("close"); assert.equal(coll.events.length, 0); assert.equal(source.listeners("close").length, 0); assert.equal(source.listeners("data").length, 0); assert.equal(source.listeners("end").length, 0); assert.equal(source.listeners("error").length, 0); } /** * Ensure that things don't go haywire if a dropper is destroyed in the * middle of being resumed. */ function destroyDuringResume() { var theData = new Buffer("stuff"); var source = new events.EventEmitter(); var dropper = new Dropper(source, { size: 5 }); var coll = new EventCollector(); dropper.pause(); coll.listenAllCommon(dropper); source.emit("data", theData); source.emit("end"); dropper.on("data", function() { dropper.destroy(); }); dropper.resume(); assert.equal(coll.events.length, 1); coll.assertEvent(0, dropper, "data", [theData]); } function test() { constructor(); constructorFailure(); readableTransition(); eventsAfterClose(); nonMultipleEventSequence(); multipleOkayEventSequence(); errorSequence(); bufferedSequence(); ifPartial(); setEncoding(); setIncomingEncoding(); commonOptions(); afterDestroy(); destroyDuringResume(); } module.exports = { test: test };