noflo
Version:
Flow-Based Programming environment for JavaScript
649 lines (635 loc) • 21 kB
JavaScript
var chai, noflo, path, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
if (typeof process !== 'undefined' && process.execPath && process.execPath.match(/node|iojs/)) {
if (!chai) {
chai = require('chai');
}
noflo = require('../src/lib/NoFlo.coffee');
path = require('path');
root = path.resolve(__dirname, '../');
} else {
noflo = require('noflo/src/lib/NoFlo.js');
root = 'noflo';
}
describe('NoFlo Network', function() {
var Callback, Merge, Split;
Split = (function(_super) {
__extends(Split, _super);
function Split() {
this.stopped = false;
this.inPorts = {
"in": new noflo.Port
};
this.outPorts = {
out: new noflo.ArrayPort
};
this.inPorts["in"].on('data', (function(_this) {
return function(data) {
return _this.outPorts.out.send(data);
};
})(this));
this.inPorts["in"].on('disconnect', (function(_this) {
return function() {
return _this.outPorts.out.disconnect();
};
})(this));
}
Split.prototype.start = function() {
this.stopped = false;
return Split.__super__.start.call(this);
};
Split.prototype.shutdown = function() {
return this.stopped = true;
};
return Split;
})(noflo.Component);
Split.getComponent = function() {
return new Split;
};
Merge = (function(_super) {
__extends(Merge, _super);
function Merge() {
this.inPorts = {
"in": new noflo.ArrayPort
};
this.outPorts = {
out: new noflo.Port
};
this.inPorts["in"].on('data', (function(_this) {
return function(data) {
return _this.outPorts.out.send(data);
};
})(this));
this.inPorts["in"].on('disconnect', (function(_this) {
return function() {
return _this.outPorts.out.disconnect();
};
})(this));
}
return Merge;
})(noflo.Component);
Merge.getComponent = function() {
return new Merge;
};
Callback = (function(_super) {
__extends(Callback, _super);
function Callback() {
this.cb = null;
this.inPorts = {
"in": new noflo.Port,
callback: new noflo.Port
};
this.outPorts = {};
this.inPorts.callback.on('data', (function(_this) {
return function(data) {
return _this.cb = data;
};
})(this));
this.inPorts["in"].on('data', (function(_this) {
return function(data) {
return _this.cb(data);
};
})(this));
}
return Callback;
})(noflo.Component);
Callback.getComponent = function() {
return new Callback;
};
describe('with an empty graph', function() {
var g, n;
g = null;
n = null;
before(function(done) {
g = new noflo.Graph;
g.baseDir = root;
n = new noflo.Network(g);
return n.connect(done);
});
it('should initially be marked as stopped', function() {
return chai.expect(n.isStarted()).to.equal(false);
});
it('should initially have no processes', function() {
return chai.expect(n.processes).to.be.empty;
});
it('should initially have to connections', function() {
return chai.expect(n.connections).to.be.empty;
});
it('should initially have no IIPs', function() {
return chai.expect(n.initials).to.be.empty;
});
it('should have reference to the graph', function() {
return chai.expect(n.graph).to.equal(g);
});
it('should know its baseDir', function() {
return chai.expect(n.baseDir).to.equal(g.baseDir);
});
it('should have a ComponentLoader', function() {
return chai.expect(n.loader).to.be.an('object');
});
it('should have transmitted the baseDir to the Component Loader', function() {
return chai.expect(n.loader.baseDir).to.equal(g.baseDir);
});
it('should be able to list components', function(done) {
this.timeout(60 * 1000);
return n.loader.listComponents(function(err, components) {
if (err) {
return done(err);
}
chai.expect(components).to.be.an('object');
return done();
});
});
it('should have an uptime', function() {
return chai.expect(n.uptime()).to.be.at.least(0);
});
return describe('with new node', function() {
it('should contain the node', function(done) {
g.once('addNode', function() {
return setTimeout(function() {
chai.expect(n.processes).not.to.be.empty;
chai.expect(n.processes.Graph).to.exist;
return done();
}, 10);
});
return g.addNode('Graph', 'Graph', {
foo: 'Bar'
});
});
it('should have transmitted the node metadata to the process', function() {
chai.expect(n.processes.Graph.component.metadata).to.exist;
chai.expect(n.processes.Graph.component.metadata).to.be.an.object;
return chai.expect(n.processes.Graph.component.metadata).to.eql(g.getNode('Graph').metadata);
});
return it('should not contain the node after removal', function(done) {
g.once('removeNode', function() {
return setTimeout(function() {
chai.expect(n.processes).to.be.empty;
return done();
}, 10);
});
return g.removeNode('Graph');
});
});
});
describe('with a simple graph', function() {
var g, n;
g = null;
n = null;
before(function(done) {
this.timeout(60 * 1000);
g = new noflo.Graph;
g.baseDir = root;
g.addNode('Merge', 'Merge');
g.addNode('Callback', 'Callback');
g.addEdge('Merge', 'out', 'Callback', 'in');
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Split = Split;
nw.loader.components.Merge = Merge;
nw.loader.components.Callback = Callback;
n = nw;
return nw.connect(function(err) {
if (err) {
return done(err);
}
nw.start();
return done();
});
}, true);
});
it('should contain two processes', function() {
chai.expect(n.processes).to.not.be.empty;
chai.expect(n.processes.Merge).to.exist;
chai.expect(n.processes.Merge).to.be.an('Object');
chai.expect(n.processes.Callback).to.exist;
return chai.expect(n.processes.Callback).to.be.an('Object');
});
it('the ports of the processes should know the node names', function() {
var name, port, _ref, _ref1, _results;
_ref = n.processes.Callback.component.inPorts;
for (name in _ref) {
port = _ref[name];
chai.expect(port.name).to.equal(name);
chai.expect(port.node).to.equal('Callback');
chai.expect(port.getId()).to.equal("Callback " + (name.toUpperCase()));
}
_ref1 = n.processes.Callback.component.outPorts;
_results = [];
for (name in _ref1) {
port = _ref1[name];
chai.expect(port.name).to.equal(name);
chai.expect(port.node).to.equal('Callback');
_results.push(chai.expect(port.getId()).to.equal("Callback " + (name.toUpperCase())));
}
return _results;
});
it('should contain one connection', function() {
chai.expect(n.connections).to.not.be.empty;
return chai.expect(n.connections.length).to.equal(1);
});
it('should call callback when receiving data', function(done) {
g.addInitial(function(data) {
chai.expect(data).to.equal('Foo');
return done();
}, 'Callback', 'callback');
g.addInitial('Foo', 'Merge', 'in');
chai.expect(n.initials).not.to.be.empty;
return n.start(function(err) {
if (err) {
return done(err);
}
});
});
it('should have started in debug mode', function() {
return chai.expect(n.debug).to.equal(true);
});
it('should emit a process-error when a component throws', function(done) {
g.removeInitial('Callback', 'callback');
g.removeInitial('Merge', 'in');
g.addInitial(function(data) {
throw new Error('got Foo');
}, 'Callback', 'callback');
g.addInitial('Foo', 'Merge', 'in');
n.once('process-error', function(err) {
chai.expect(err).to.be.an('object');
chai.expect(err.error.message).to.equal('got Foo');
return done();
});
return n.sendInitials();
});
describe('once started', function() {
return it('should be marked as started', function() {
return chai.expect(n.isStarted()).to.equal(true);
});
});
describe('with a renamed node', function() {
it('should have the process in a new location', function(done) {
g.once('renameNode', function() {
chai.expect(n.processes.Func).to.be.an('object');
return done();
});
return g.renameNode('Callback', 'Func');
});
it('shouldn\'t have the process in the old location', function() {
return chai.expect(n.processes.Callback).to.be.undefined;
});
return it('should have informed the ports of their new node name', function() {
var name, port, _ref, _ref1, _results;
_ref = n.processes.Func.component.inPorts;
for (name in _ref) {
port = _ref[name];
chai.expect(port.name).to.equal(name);
chai.expect(port.node).to.equal('Func');
chai.expect(port.getId()).to.equal("Func " + (name.toUpperCase()));
}
_ref1 = n.processes.Func.component.outPorts;
_results = [];
for (name in _ref1) {
port = _ref1[name];
chai.expect(port.name).to.equal(name);
chai.expect(port.node).to.equal('Func');
_results.push(chai.expect(port.getId()).to.equal("Func " + (name.toUpperCase())));
}
return _results;
});
});
describe('with process icon change', function() {
return it('should emit an icon event', function(done) {
n.once('icon', function(data) {
chai.expect(data).to.be.an('object');
chai.expect(data.id).to.equal('Func');
chai.expect(data.icon).to.equal('flask');
return done();
});
return n.processes.Func.component.setIcon('flask');
});
});
return describe('once stopped', function() {
return it('should be marked as stopped', function(done) {
n.stop();
chai.expect(n.isStarted()).to.equal(false);
return done();
});
});
});
describe('with nodes containing default ports', function() {
var c, cb, g, testCallback;
g = null;
testCallback = null;
c = null;
cb = null;
beforeEach(function() {
testCallback = null;
c = null;
cb = null;
c = new noflo.Component;
c.inPorts.add('in', {
required: true,
datatype: 'string',
"default": 'default-value'
}, (function(_this) {
return function(e, data) {
if (e === 'data') {
c.outPorts.out.send(data);
return c.outPorts.out.disconnect();
}
};
})(this));
c.outPorts.add('out');
cb = new noflo.Component;
cb.inPorts.add('in', {
required: true,
datatype: 'all'
}, (function(_this) {
return function(e, data) {
if (e === 'data') {
return testCallback(data);
}
};
})(this));
g = new noflo.Graph;
g.baseDir = root;
g.addNode('Def', 'Def');
g.addNode('Cb', 'Cb');
return g.addEdge('Def', 'out', 'Cb', 'in');
});
it('should send default values to nodes without an edge', function(done) {
this.timeout(60 * 1000);
testCallback = function(data) {
chai.expect(data).to.equal('default-value');
return done();
};
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Def = function() {
return c;
};
nw.loader.components.Cb = function() {
return cb;
};
return nw.connect(function(err) {
if (err) {
return done(err);
}
return nw.start();
});
}, true);
});
it('should not send default values to nodes with an edge', function(done) {
this.timeout(60 * 1000);
testCallback = function(data) {
chai.expect(data).to.equal('from-edge');
return done();
};
g.addNode('Merge', 'Merge');
g.addEdge('Merge', 'out', 'Def', 'in');
g.addInitial('from-edge', 'Merge', 'in');
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Def = function() {
return c;
};
nw.loader.components.Cb = function() {
return cb;
};
nw.loader.components.Merge = Merge;
return nw.connect(function(err) {
if (err) {
return done(err);
}
return nw.start();
});
}, true);
});
return it('should not send default values to nodes with IIP', function(done) {
this.timeout(60 * 1000);
testCallback = function(data) {
chai.expect(data).to.equal('from-IIP');
return done();
};
g.addInitial('from-IIP', 'Def', 'in');
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Def = function() {
return c;
};
nw.loader.components.Cb = function() {
return cb;
};
nw.loader.components.Merge = Merge;
return nw.connect(function(err) {
if (err) {
return done(err);
}
return nw.start();
});
}, true);
});
});
describe("Nodes are added first, then edges, then initializers (i.e. IIPs), and in order of definition order within each", function() {
var actual, expected, g, n, restore, stub, stubbed;
g = null;
n = null;
stubbed = {};
actual = [];
expected = [];
stub = function() {
stubbed.addNode = noflo.Network.prototype.addNode;
stubbed.addEdge = noflo.Network.prototype.addEdge;
stubbed.addInitial = noflo.Network.prototype.addInitial;
noflo.Network.prototype.addNode = function(node, cb) {
actual.push(node);
return stubbed.addNode.call(this, node, cb);
};
noflo.Network.prototype.addEdge = function(edge, cb) {
actual.push(edge);
return stubbed.addEdge.call(this, edge, cb);
};
return noflo.Network.prototype.addInitial = function(initial, cb) {
actual.push(initial);
return stubbed.addInitial.call(this, initial, cb);
};
};
restore = function() {
noflo.Network.prototype.addNode = stubbed.addNode;
noflo.Network.prototype.addEdge = stubbed.addEdge;
return noflo.Network.prototype.addInitial = stubbed.addInitial;
};
before(function(done) {
stub();
this.timeout(6000);
g = new noflo.Graph;
g.baseDir = root;
expected[0] = g.addNode("D", "Callback");
expected[10] = g.addInitial((function() {}), "D", "callback");
expected[1] = g.addNode("A", "Split");
expected[11] = g.addInitial("Hello", "A", "in");
expected[2] = g.addNode("B1", "Merge");
expected[3] = g.addNode("B2", "Merge");
expected[5] = g.addEdge("A", "out", "B1", "in");
expected[6] = g.addEdge("A", "out", "B2", "in");
expected[4] = g.addNode("C", "Merge");
expected[7] = g.addEdge("B1", "out", "C", "in");
expected[12] = g.addInitial("World", "C", "in");
expected[8] = g.addEdge("B2", "out", "C", "in");
expected[9] = g.addEdge("C", "out", "D", "in");
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Split = Split;
nw.loader.components.Merge = Merge;
nw.loader.components.Callback = Callback;
n = nw;
return nw.connect(function(err) {
if (err) {
return done(err);
}
nw.start();
return done();
});
}, true);
});
after(restore);
return it("should add nodes, edges, and initials, in that order", function() {
return chai.expect(actual).to.deep.equal(expected);
});
});
describe('with an existing IIP', function() {
var g, n;
g = null;
n = null;
before(function() {
g = new noflo.Graph;
g.baseDir = root;
g.addNode('Callback', 'Callback');
g.addNode('Repeat', 'Split');
return g.addEdge('Repeat', 'out', 'Callback', 'in');
});
it('should call the Callback with the original IIP value', function(done) {
var cb;
this.timeout(6000);
cb = function(packet) {
chai.expect(packet).to.equal('Foo');
return done();
};
g.addInitial(cb, 'Callback', 'callback');
g.addInitial('Foo', 'Repeat', 'in');
return setTimeout(function() {
return noflo.createNetwork(g, function(err, nw) {
if (err) {
return done(err);
}
nw.loader.components.Split = Split;
nw.loader.components.Merge = Merge;
nw.loader.components.Callback = Callback;
n = nw;
return nw.connect(function(err) {
if (err) {
return done(err);
}
return nw.start();
});
}, true);
}, 10);
});
it('should allow removing the IIPs', function(done) {
var onRemove, removed;
this.timeout(6000);
removed = 0;
onRemove = function() {
removed++;
if (removed < 2) {
return;
}
chai.expect(n.initials.length).to.equal(0, 'No IIPs left');
chai.expect(n.connections.length).to.equal(1, 'Only one connection');
g.removeListener('removeInitial', onRemove);
return done();
};
g.on('removeInitial', onRemove);
g.removeInitial('Callback', 'callback');
return g.removeInitial('Repeat', 'in');
});
it('new IIPs to replace original ones should work correctly', function(done) {
var cb;
cb = function(packet) {
chai.expect(packet).to.equal('Baz');
return done();
};
g.addInitial(cb, 'Callback', 'callback');
g.addInitial('Baz', 'Repeat', 'in');
return n.start();
});
return describe('on stopping', function() {
it('processes should be running before the stop call', function() {
return chai.expect(n.processes.Repeat.component.stopped).to.equal(false);
});
it('should emit the end event', function(done) {
this.timeout(5000);
n.once('end', function(endTimes) {
chai.expect(endTimes).to.be.an('object');
return done();
});
return n.stop();
});
return it('should have called the shutdown method of each process', function() {
return chai.expect(n.processes.Repeat.component.stopped).to.equal(true);
});
});
});
return describe('with a very large network', function() {
return it('should be able to connect without errors', function(done) {
var called, g, n, nw, _i, _j, _k;
this.timeout(100000);
g = new noflo.Graph;
g.baseDir = root;
called = 0;
for (n = _i = 0; _i <= 10000; n = ++_i) {
g.addNode("Repeat" + n, 'Split');
}
g.addNode('Callback', 'Callback');
for (n = _j = 0; _j <= 10000; n = ++_j) {
g.addEdge("Repeat" + n, 'out', 'Callback', 'in');
}
g.addInitial(function() {
return called++;
}, 'Callback', 'callback');
for (n = _k = 0; _k <= 10000; n = ++_k) {
g.addInitial(n, "Repeat" + n, 'in');
}
nw = new noflo.Network(g);
return nw.loader.listComponents(function(err) {
if (err) {
return done(err);
}
nw.loader.components.Split = Split;
nw.loader.components.Callback = Callback;
nw.once('end', function() {
chai.expect(called).to.equal(10001);
return done();
});
return nw.connect(function(err) {
if (err) {
return done(err);
}
return nw.start(function(err) {
if (err) {
return done(err);
}
});
});
});
});
});
});