UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

640 lines (637 loc) 20.3 kB
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); } }); }); }); }); }); });