noflo
Version:
Flow-Based Programming environment for JavaScript
458 lines (424 loc) • 15.6 kB
text/coffeescript
if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/
chai = require 'chai' unless chai
noflo = require '../src/lib/NoFlo.coffee'
path = require 'path'
root = path.resolve __dirname, '../'
else
noflo = require 'noflo'
root = 'noflo'
describe 'NoFlo Network', ->
Split = ->
new noflo.Component
inPorts:
in: datatype: 'all'
outPorts:
out: datatype: 'all'
process: (input, output) ->
output.sendDone
out: input.get 'in'
Merge = ->
new noflo.Component
inPorts:
in: datatype: 'all'
outPorts:
out: datatype: 'all'
process: (input, output) ->
output.sendDone
out: input.get 'in'
Callback = ->
new noflo.Component
inPorts:
in: datatype: 'all'
callback:
datatype: 'all'
control: true
process: (input, output) ->
# Drop brackets
return unless input.hasData 'callback', 'in'
cb = input.getData 'callback'
data = input.getData 'in'
cb data
output.done()
describe 'with an empty graph', ->
g = null
n = null
before (done) ->
g = new noflo.Graph
g.baseDir = root
n = new noflo.Network g
n.connect done
it 'should initially be marked as stopped', ->
chai.expect(n.isStarted()).to.equal false
it 'should initially have no processes', ->
chai.expect(n.processes).to.be.empty
it 'should initially have to connections', ->
chai.expect(n.connections).to.be.empty
it 'should initially have no IIPs', ->
chai.expect(n.initials).to.be.empty
it 'should have reference to the graph', ->
chai.expect(n.graph).to.equal g
it 'should know its baseDir', ->
chai.expect(n.baseDir).to.equal g.baseDir
it 'should have a ComponentLoader', ->
chai.expect(n.loader).to.be.an 'object'
it 'should have transmitted the baseDir to the Component Loader', ->
chai.expect(n.loader.baseDir).to.equal g.baseDir
it 'should be able to list components', (done) ->
@timeout 60 * 1000
n.loader.listComponents (err, components) ->
return done err if err
chai.expect(components).to.be.an 'object'
done()
return
it 'should have an uptime', ->
chai.expect(n.uptime()).to.be.at.least 0
describe 'with new node', ->
it 'should contain the node', (done) ->
g.once 'addNode', ->
setTimeout ->
chai.expect(n.processes).not.to.be.empty
chai.expect(n.processes.Graph).to.exist
done()
, 10
g.addNode 'Graph', 'Graph',
foo: 'Bar'
it 'should have transmitted the node metadata to the process', ->
chai.expect(n.processes.Graph.component.metadata).to.exist
chai.expect(n.processes.Graph.component.metadata).to.be.an.object
chai.expect(n.processes.Graph.component.metadata).to.eql g.getNode('Graph').metadata
it 'should not contain the node after removal', (done) ->
g.once 'removeNode', ->
setTimeout ->
chai.expect(n.processes).to.be.empty
done()
, 10
g.removeNode 'Graph'
describe 'with a simple graph', ->
g = null
n = null
cb = null
before (done) ->
@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 (data) ->
chai.expect(data).to.equal 'Foo'
cb()
, 'Callback', 'callback'
g.addInitial 'Foo', 'Merge', 'in'
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Split = Split
nw.loader.components.Merge = Merge
nw.loader.components.Callback = Callback
n = nw
nw.connect (err) ->
return done err if err
done()
, true
it 'should send some initials when started', (done) ->
chai.expect(n.initials).not.to.be.empty
cb = done
n.start (err) ->
return done err if err
it 'should contain two processes', ->
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
chai.expect(n.processes.Callback).to.be.an 'Object'
it 'the ports of the processes should know the node names', ->
for name, port of n.processes.Callback.component.inPorts.ports
chai.expect(port.name).to.equal name
chai.expect(port.node).to.equal 'Callback'
chai.expect(port.getId()).to.equal "Callback #{name.toUpperCase()}"
for name, port of n.processes.Callback.component.outPorts.ports
chai.expect(port.name).to.equal name
chai.expect(port.node).to.equal 'Callback'
chai.expect(port.getId()).to.equal "Callback #{name.toUpperCase()}"
it 'should contain 1 connection between processes and 2 for IIPs', ->
chai.expect(n.connections).to.not.be.empty
chai.expect(n.connections.length).to.equal 3
it 'should have started in debug mode', ->
chai.expect(n.debug).to.equal true
it 'should emit a process-error when a component throws', (done) ->
g.removeInitial 'Callback', 'callback'
g.removeInitial 'Merge', 'in'
g.addInitial (data) ->
throw new Error 'got Foo'
, 'Callback', 'callback'
g.addInitial 'Foo', 'Merge', 'in'
n.once 'process-error', (err) ->
chai.expect(err).to.be.an 'object'
chai.expect(err.error.message).to.equal 'got Foo'
done()
n.sendInitials()
describe 'once started', ->
it 'should be marked as started', ->
chai.expect(n.isStarted()).to.equal true
describe 'with a renamed node', ->
it 'should have the process in a new location', (done) ->
g.once 'renameNode', ->
chai.expect(n.processes.Func).to.be.an 'object'
done()
g.renameNode 'Callback', 'Func'
it 'shouldn\'t have the process in the old location', ->
chai.expect(n.processes.Callback).to.be.undefined
it 'should have informed the ports of their new node name', ->
for name, port of n.processes.Func.component.inPorts.ports
chai.expect(port.name).to.equal name
chai.expect(port.node).to.equal 'Func'
chai.expect(port.getId()).to.equal "Func #{name.toUpperCase()}"
for name, port of n.processes.Func.component.outPorts.ports
chai.expect(port.name).to.equal name
chai.expect(port.node).to.equal 'Func'
chai.expect(port.getId()).to.equal "Func #{name.toUpperCase()}"
describe 'with process icon change', ->
it 'should emit an icon event', (done) ->
n.once 'icon', (data) ->
chai.expect(data).to.be.an 'object'
chai.expect(data.id).to.equal 'Func'
chai.expect(data.icon).to.equal 'flask'
done()
n.processes.Func.component.setIcon 'flask'
describe 'once stopped', ->
it 'should be marked as stopped', (done) ->
n.stop ->
chai.expect(n.isStarted()).to.equal false
done()
describe 'with nodes containing default ports', ->
g = null
testCallback = null
c = null
cb = null
beforeEach ->
testCallback = null
c = null
cb = null
c = new noflo.Component
c.inPorts.add 'in',
required: true
datatype: 'string'
default: 'default-value',
(e, data) =>
if e is 'data'
c.outPorts.out.send data
c.outPorts.out.disconnect()
c.outPorts.add 'out'
cb = new noflo.Component
cb.inPorts.add 'in',
required: true
datatype: 'all'
(e, data) =>
if e is 'data'
testCallback(data)
g = new noflo.Graph
g.baseDir = root
g.addNode 'Def', 'Def'
g.addNode 'Cb', 'Cb'
g.addEdge 'Def', 'out', 'Cb', 'in'
it 'should send default values to nodes without an edge', (done) ->
@timeout 60 * 1000
testCallback = (data) ->
chai.expect(data).to.equal 'default-value'
done()
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Def = -> c
nw.loader.components.Cb = -> cb
nw.connect (err) ->
return done err if err
nw.start (err) ->
return done err if err
, true
it 'should not send default values to nodes with an edge', (done) ->
@timeout 60 * 1000
testCallback = (data) ->
chai.expect(data).to.equal 'from-edge'
done()
g.addNode 'Merge', 'Merge'
g.addEdge 'Merge', 'out', 'Def', 'in'
g.addInitial 'from-edge', 'Merge', 'in'
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Def = -> c
nw.loader.components.Cb = -> cb
nw.loader.components.Merge = Merge
nw.connect (err) ->
return done err if err
nw.start (err) ->
return done err if err
, true
it 'should not send default values to nodes with IIP', (done) ->
@timeout 60 * 1000
testCallback = (data) ->
chai.expect(data).to.equal 'from-IIP'
done()
g.addInitial 'from-IIP', 'Def', 'in'
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Def = -> c
nw.loader.components.Cb = -> cb
nw.loader.components.Merge = Merge
nw.connect (err) ->
return done err if err
nw.start (err) ->
return done err if err
, true
describe "Nodes are added first, then edges, then initializers (i.e. IIPs), and in order of definition order within each", ->
g = null
n = null
stubbed = {}
actual = []
expected = []
# Poor man's way of stubbing the Network. Investigate using
# [sinon-chai](https://github.com/domenic/sinon-chai) when we need stubbing
# for other parts of testing as well.
stub = ->
stubbed.addNode = noflo.Network::addNode
stubbed.addEdge = noflo.Network::addEdge
stubbed.addInitial = noflo.Network::addInitial
# Record the node/edge/initial and pass it along
noflo.Network::addNode = (node, cb) ->
actual.push node
stubbed.addNode.call this, node, cb
noflo.Network::addEdge = (edge, cb) ->
actual.push edge
stubbed.addEdge.call this, edge, cb
noflo.Network::addInitial = (initial, cb) ->
actual.push initial
stubbed.addInitial.call this, initial, cb
# Clean up after ourselves
restore = ->
noflo.Network::addNode = stubbed.addNode
noflo.Network::addEdge = stubbed.addEdge
noflo.Network::addInitial = stubbed.addInitial
before (done) ->
stub()
@timeout 6000
g = new noflo.Graph
g.baseDir = root
# Save the nodes/edges/initial for order testing later. The index numbers
# are the expected positions.
expected[0] = g.addNode "D", "Callback"
expected[10] = g.addInitial (->), "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"
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Split = Split
nw.loader.components.Merge = Merge
nw.loader.components.Callback = Callback
n = nw
nw.connect (err) ->
return done err if err
nw.start done
, true
after restore
it "should add nodes, edges, and initials, in that order", ->
chai.expect(actual).to.deep.equal expected
describe 'with an existing IIP', ->
g = null
n = null
before ->
g = new noflo.Graph
g.baseDir = root
g.addNode 'Callback', 'Callback'
g.addNode 'Repeat', 'Split'
g.addEdge 'Repeat', 'out', 'Callback', 'in'
it 'should call the Callback with the original IIP value', (done) ->
@timeout 6000
cb = (packet) ->
chai.expect(packet).to.equal 'Foo'
done()
g.addInitial cb, 'Callback', 'callback'
g.addInitial 'Foo', 'Repeat', 'in'
setTimeout ->
noflo.createNetwork g, (err, nw) ->
return done err if err
nw.loader.components.Split = Split
nw.loader.components.Merge = Merge
nw.loader.components.Callback = Callback
n = nw
nw.connect (err) ->
return done err if err
nw.start (err) ->
return done err if err
, true
, 10
it 'should allow removing the IIPs', (done) ->
@timeout 6000
removed = 0
onRemove = ->
removed++
return if removed < 2
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
done()
g.on 'removeInitial', onRemove
g.removeInitial 'Callback', 'callback'
g.removeInitial 'Repeat', 'in'
it 'new IIPs to replace original ones should work correctly', (done) ->
cb = (packet) ->
chai.expect(packet).to.equal 'Baz'
done()
g.addInitial cb, 'Callback', 'callback'
g.addInitial 'Baz', 'Repeat', 'in'
n.start (err) ->
return done err if err
describe 'on stopping', ->
it 'processes should be running before the stop call', ->
chai.expect(n.started).to.be.true
chai.expect(n.processes.Repeat.component.started).to.equal true
it 'should emit the end event', (done) ->
@timeout 5000
# Ensure we have a connection open
n.once 'end', (endTimes) ->
chai.expect(endTimes).to.be.an 'object'
done()
n.stop (err) ->
return done err if err
it 'should have called the shutdown method of each process', ->
chai.expect(n.processes.Repeat.component.started).to.equal false
describe 'with a very large network', ->
it 'should be able to connect without errors', (done) ->
@timeout 100000
g = new noflo.Graph
g.baseDir = root
called = 0
for n in [0..10000]
g.addNode "Repeat#{n}", 'Split'
g.addNode 'Callback', 'Callback'
for n in [0..10000]
g.addEdge "Repeat#{n}", 'out', 'Callback', 'in'
g.addInitial ->
called++
, 'Callback', 'callback'
for n in [0..10000]
g.addInitial n, "Repeat#{n}", 'in'
nw = new noflo.Network g
nw.loader.listComponents (err) ->
return done err if err
nw.loader.components.Split = Split
nw.loader.components.Callback = Callback
nw.once 'end', ->
chai.expect(called).to.equal 10001
done()
nw.connect (err) ->
return done err if err
nw.start (err) ->
return done err if err
return