pipette
Version:
Stream and pipe utilities for Node
574 lines (467 loc) • 13.9 kB
JavaScript
// Copyright 2012 The Obvious Corporation.
/*
* Modules used
*/
"use strict";
var assert = require("assert");
var events = require("events");
var Blip = require("../").Blip;
var Sink = require("../").Sink;
var EventCollector = require("./eventcoll").EventCollector;
var emit = require("./emit").emit;
/*
* Tests
*/
/**
* Make sure the constructor doesn't fail off the bat.
*/
function constructor() {
new Sink(new events.EventEmitter());
new Sink(new events.EventEmitter(), {});
new Sink(new events.EventEmitter(), { encoding: "hex" });
new Sink(new events.EventEmitter(), { incomingEncoding: "hex" });
new Sink(new events.EventEmitter(), { paused: true });
new Sink(new events.EventEmitter(), { paused: false });
}
/**
* Test expected constructor failures.
*/
function constructorFailure() {
function f1() {
new Sink();
}
assert.throws(f1, /Missing source/);
function f2() {
new Sink(["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 Sink(bad);
}
assert.throws(f3, /Source already ended./);
function f4() {
new Sink(new Blip(), { encoding: 12 });
}
assert.throws(f4, /Bad value for option: encoding/);
function f5() {
new Sink(new Blip(), { incomingEncoding: "zorch" });
}
assert.throws(f5, /Bad value for option: incomingEncoding/);
function f6() {
new Sink(new Blip(), { paused: undefined });
}
assert.throws(f6, /Bad value for option: paused/);
function f7() {
new Sink(new Blip(), { zorchSplat: undefined });
}
assert.throws(f7, /Unknown option: zorchSplat/);
}
/**
* Test that invalid encodings are rejected.
*/
function badEncodings() {
var sink = new Sink(new events.EventEmitter());
function f1() {
sink.setEncoding("blort");
}
assert.throws(f1, /Invalid encoding name/);
function f2() {
sink.setIncomingEncoding("biff");
}
assert.throws(f2, /Invalid encoding name/);
}
/**
* Test that no events get added spontaneously.
*/
function noInitialEvents() {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
sink.pause();
coll.listenAllCommon(sink);
sink.resume();
assert.equal(coll.events.length, 0);
}
/**
* Test that `readable` is true until an end-type event comes through.
*/
function readableTransition() {
tryWith(false, "end");
tryWith(false, "close");
tryWith(false, "error", new Error("criminy"));
tryWith(true, "end");
tryWith(true, "close");
tryWith(true, "error", new Error("criminy"));
function tryWith(pauseFirst, name, arg) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
assert.ok(sink.readable);
if (pauseFirst) {
sink.pause();
}
emit(source, name, arg);
if (pauseFirst) {
assert.equal(coll.events.length, 0);
assert.ok(sink.readable);
sink.resume();
}
assert.equal(coll.events.length, 2);
assert.ok(!sink.readable);
}
}
/**
* Test that no further events get emitted after an end-type event.
*/
function eventsAfterEnd() {
tryWith(false, "end");
tryWith(false, "close");
tryWith(false, "error", new Error("oy"));
tryWith(true, "end");
tryWith(true, "close");
tryWith(true, "error", new Error("oy"));
function tryWith(extraData, name, arg) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
emit(source, name, arg);
assert.equal(coll.events.length, 2);
coll.reset();
if (extraData) {
source.emit("data", "huzzah");
assert.equal(coll.events.length, 0);
}
source.emit("end");
assert.equal(coll.events.length, 0);
source.emit("close");
assert.equal(coll.events.length, 0);
source.emit("error", new Error("eek"));
assert.equal(coll.events.length, 0);
}
}
/**
* Test a few no-data-events cases.
*/
function noDataEvents() {
tryWith("end", undefined);
tryWith("close", undefined);
tryWith("error", new Error("yow"));
function tryWith(endEvent, endArg) {
var isError = (endEvent === "error");
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
emit(source, endEvent, endArg);
assert.equal(coll.events.length, 2);
if (isError) {
coll.assertEvent(0, sink, "error", [endArg]);
} else {
coll.assertEvent(0, sink, "end", undefined);
coll.assertEvent(1, sink, "close", undefined);
}
}
}
/**
* Test a few single data event cases.
*/
function singleDataEvent() {
var theData = new Buffer("stuff");
tryWith("end", undefined);
tryWith("close", undefined);
tryWith("error", new Error("yow"));
function tryWith(endEvent, endArg) {
var isError = (endEvent === "error");
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
source.emit("data", theData);
emit(source, endEvent, endArg);
assert.equal(coll.events.length, 3);
coll.assertEvent(0, sink, "data", [theData]);
if (isError) {
coll.assertEvent(1, sink, "error", [endArg]);
} else {
coll.assertEvent(1, sink, "end", undefined);
}
coll.assertEvent(2, sink, "close", undefined);
}
}
/**
* Test the collection of multiple data events.
*/
function multipleDataEvents() {
for (var i = 1; i < 200; i += 17) {
tryWith(i);
}
function tryWith(count) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
var expect = "";
coll.listenAllCommon(sink);
for (var i = 0; i < count; i++) {
var buf = bufFor(i);
expect += buf.toString();
source.emit("data", buf);
}
assert.equal(coll.events.length, 0);
source.emit("end");
assert.equal(coll.events.length, 3);
coll.assertEvent(0, sink, "data", [new Buffer(expect)]);
coll.assertEvent(1, sink, "end", undefined);
coll.assertEvent(2, sink, "close", undefined);
}
function bufFor(val) {
return new Buffer("#" + val);
}
}
/**
* Test that a `close` event without an "errorish" payload gets properly
* relayed as a no-payload event.
*/
function closeWithoutPayload() {
tryWith(undefined);
tryWith(false);
function tryWith(payload) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
source.emit("close", payload);
assert.equal(coll.events.length, 2);
coll.assertEvent(0, sink, "end");
coll.assertEvent(1, sink, "close");
}
}
/**
* Test that a `close` event with a payload gets properly split into
* an `error` and then a `close` event.
*/
function closeWithPayload() {
tryWith(true);
tryWith(new Error("yowza"));
tryWith(["You never know when you might get an array."]);
function tryWith(payload) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
source.emit("close", payload);
assert.equal(coll.events.length, 2);
coll.assertEvent(0, sink, "error", [payload]);
coll.assertEvent(1, sink, "close");
}
}
/**
* Check that emit-side encoding works as expected.
*/
function setEncoding() {
tryWith(undefined);
tryWith("ascii");
tryWith("base64");
tryWith("hex");
tryWith("ucs2");
tryWith("utf8");
tryWith("utf16le", "ucs2");
function tryWith(enc, expectEnc) {
expectEnc = expectEnc || enc; // See codec.setEncoding() implementation.
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
source.emit("data", "testing");
source.emit("data", "123");
sink.setEncoding(enc);
source.emit("end");
var expect = new Buffer("testing123");
if (enc) {
expect = expect.toString(expectEnc);
}
coll.assertEvent(0, sink, "data", [expect]);
coll.assertEvent(1, sink, "end", undefined);
coll.assertEvent(2, sink, "close", undefined);
}
}
/**
* Test that `setIncomingEncoding()` operates properly.
*/
function setIncomingEncoding() {
var source = new events.EventEmitter();
var sink = new Sink(source);
// Default to utf-8.
source.emit("data", "\u168c-gort"); // "OGHAM LETTER GORT"
sink.setIncomingEncoding("base64");
source.emit("data", "LWJpc2N1aXRzCg=="); // "-biscuits\n"
sink.setIncomingEncoding("ascii");
source.emit("data", "scones");
sink.setIncomingEncoding("utf8");
source.emit("data", "-\u1683-fearn"); // "OGHAM LETTER FEARN"
source.emit("end");
assert.equal(sink.getData().toString(),
"\u168c-gort-biscuits\nscones-\u1683-fearn");
}
/**
* Tests the common constructor options.
*/
function commonOptions() {
var theData = new Buffer("muffinberry scone", "ucs2");
var source = new events.EventEmitter();
var sink = new Sink(source,
{ encoding: "base64",
incomingEncoding: "ucs2",
paused: true });
var coll = new EventCollector();
coll.listenAllCommon(sink);
source.emit("data", theData.toString("ucs2"));
source.emit("end");
source.emit("close");
assert.ok(sink.readable);
assert.equal(coll.events.length, 0);
sink.resume();
assert.ok(!sink.readable);
assert.equal(coll.events.length, 3);
coll.assertEvent(0, sink, "data", [theData.toString("base64")]);
coll.assertEvent(1, sink, "end");
coll.assertEvent(2, sink, "close");
assert.equal(sink.getData(), theData.toString("base64"));
}
/**
* Ensure that no events get passed after a `destroy()` call. Also, proves
* that the valve isn't even listening for events from the source anymore.
*/
function afterDestroy() {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
coll.listenAllCommon(sink);
sink.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 sink is destroyed in the
* middle of being resumed.
*/
function destroyDuringResume() {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
sink.on("data", function() { sink.destroy(); });
coll.listenAllCommon(sink);
source.emit("data", "stuff");
source.emit("end");
assert.equal(coll.events.length, 3);
coll.assertEvent(0, sink, "data", [new Buffer("stuff")]);
}
/**
* Test that `getData()` returns `undefined` before there is data
* and an appropriate value after.
*/
function appropriateGetData() {
var theData = new Buffer("stuffy stuff");
tryWith(false, "end", undefined);
tryWith(false, "close", undefined);
tryWith(false, "error", new Error("yow"));
tryWith(true, "end", undefined);
tryWith(true, "close", undefined);
tryWith(true, "error", new Error("yow"));
function tryWith(doData, endEvent, endArg) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var expect = doData ? theData : undefined;
var coll = new EventCollector();
assert.equal(sink.getData(), undefined);
coll.listenAllCommon(sink); // just to capture the error, if any
if (doData) {
source.emit("data", theData);
assert.equal(sink.getData(), undefined);
}
emit(source, endEvent, endArg);
assert.equal(sink.getData(), expect);
}
}
/**
* Test that `getError()` returns `undefined` before there is an error
* and an appropriate value after.
*/
function appropriateGetError() {
var theError = new Error("Missing muffin");
tryWith(undefined, false);
tryWith(undefined, true);
tryWith(theError, false);
tryWith(theError, true);
function tryWith(error, doData) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
assert.equal(sink.getError(), undefined);
coll.listenAllCommon(sink); // just to capture the error
if (doData) {
source.emit("data", "howdy");
assert.equal(sink.getError(), undefined);
}
source.emit("error", error);
assert.equal(sink.getError(), error);
}
}
/**
* Test that `gotError()` returns `false` before there is an error
* and `true` after.
*/
function appropriateGotError() {
var theError = new Error("Missing scone");
tryWith(undefined, false);
tryWith(undefined, true);
tryWith(theError, false);
tryWith(theError, true);
function tryWith(error, doData) {
var source = new events.EventEmitter();
var sink = new Sink(source);
var coll = new EventCollector();
assert.ok(!sink.gotError());
coll.listenAllCommon(sink); // just to capture the error
if (doData) {
source.emit("data", "howdy");
assert.ok(!sink.gotError());
}
source.emit("error", error);
assert.ok(sink.gotError());
}
}
function test() {
constructor();
constructorFailure();
badEncodings();
noInitialEvents();
readableTransition();
eventsAfterEnd();
noDataEvents();
singleDataEvent();
multipleDataEvents();
closeWithoutPayload();
closeWithPayload();
setEncoding();
setIncomingEncoding();
commonOptions();
afterDestroy();
destroyDuringResume();
appropriateGetData();
appropriateGetError();
appropriateGotError();
}
module.exports = {
test: test
};