noflo
Version:
Flow-Based Programming environment for JavaScript
640 lines (637 loc) • 20.3 kB
JavaScript
var chai, noflo, path, root;
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');
root = 'noflo';
}
describe('NoFlo Network', function() {
var Callback, Merge, Split;
Split = function() {
return new noflo.Component({
inPorts: {
"in": {
datatype: 'all'
}
},
outPorts: {
out: {
datatype: 'all'
}
},
process: function(input, output) {
return output.sendDone({
out: input.get('in')
});
}
});
};
Merge = function() {
return new noflo.Component({
inPorts: {
"in": {
datatype: 'all'
}
},
outPorts: {
out: {
datatype: 'all'
}
},
process: function(input, output) {
return output.sendDone({
out: input.get('in')
});
}
});
};
Callback = function() {
return new noflo.Component({
inPorts: {
"in": {
datatype: 'all'
},
callback: {
datatype: 'all',
control: true
}
},
process: function(input, output) {
var cb, data;
if (!input.hasData('callback', 'in')) {
return;
}
cb = input.getData('callback');
data = input.getData('in');
cb(data);
return output.done();
}
});
};
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);
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 cb, g, n;
g = null;
n = null;
cb = 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');
g.addInitial(function(data) {
chai.expect(data).to.equal('Foo');
return cb();
}, 'Callback', 'callback');
g.addInitial('Foo', 'Merge', '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);
}
return done();
});
}, true);
});
it('should send some initials when started', function(done) {
chai.expect(n.initials).not.to.be.empty;
cb = done;
return n.start(function(err) {
if (err) {
return done(err);
}
});
});
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.ports;
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.ports;
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 1 connection between processes and 2 for IIPs', function() {
chai.expect(n.connections).to.not.be.empty;
return chai.expect(n.connections.length).to.equal(3);
});
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.ports;
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.ports;
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) {
return n.stop(function() {
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(function(err) {
if (err) {
return done(err);
}
});
});
}, 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(function(err) {
if (err) {
return done(err);
}
});
});
}, 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(function(err) {
if (err) {
return done(err);
}
});
});
}, 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);
}
return nw.start(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(function(err) {
if (err) {
return done(err);
}
});
});
}, 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(function(err) {
if (err) {
return done(err);
}
});
});
return describe('on stopping', function() {
it('processes should be running before the stop call', function() {
chai.expect(n.started).to.be["true"];
return chai.expect(n.processes.Repeat.component.started).to.equal(true);
});
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(function(err) {
if (err) {
return done(err);
}
});
});
return it('should have called the shutdown method of each process', function() {
return chai.expect(n.processes.Repeat.component.started).to.equal(false);
});
});
});
return describe('with a very large network', function() {
return it('should be able to connect without errors', function(done) {
var called, g, i, j, k, n, nw;
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);
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);
}
});
});
});
});
});
});