UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

649 lines (635 loc) 21 kB
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); } }); }); }); }); }); });