noflo
Version:
Flow-Based Programming environment for JavaScript
700 lines (665 loc) • 21 kB
JavaScript
describe('Scope isolation', () => {
let loader = null;
const processAsync = function () {
const c = new noflo.Component();
c.inPorts.add('in',
{ datatype: 'string' });
c.outPorts.add('out',
{ datatype: 'string' });
c.process((input, output) => {
const data = input.getData('in');
setTimeout(() => output.sendDone(data + c.nodeId),
1);
});
return c;
};
const processMerge = function () {
const c = new noflo.Component();
c.inPorts.add('in1',
{ datatype: 'string' });
c.inPorts.add('in2',
{ datatype: 'string' });
c.outPorts.add('out',
{ datatype: 'string' });
c.forwardBrackets = { in1: ['out'] };
c.process((input, output) => {
if (!input.has('in1', 'in2', (ip) => ip.type === 'data')) { return; }
const first = input.getData('in1');
const second = input.getData('in2');
output.sendDone({ out: `1${first}:2${second}:${c.nodeId}` });
});
return c;
};
const processMergeUnscoped = function () {
const c = new noflo.Component();
c.inPorts.add('in1',
{ datatype: 'string' });
c.inPorts.add('in2', {
datatype: 'string',
scoped: false,
});
c.outPorts.add('out',
{ datatype: 'string' });
c.forwardBrackets = { in1: ['out'] };
c.process((input, output) => {
if (!input.has('in1', 'in2', (ip) => ip.type === 'data')) { return; }
const first = input.getData('in1');
const second = input.getData('in2');
output.sendDone({ out: `1${first}:2${second}:${c.nodeId}` });
});
return c;
};
const processUnscope = function () {
const c = new noflo.Component();
c.inPorts.add('in',
{ datatype: 'string' });
c.outPorts.add('out', {
datatype: 'string',
scoped: false,
});
c.process((input, output) => {
const data = input.getData('in');
setTimeout(() => {
output.sendDone(data + c.nodeId);
},
1);
});
return c;
};
// Merge with an addressable port
const processMergeA = function () {
const c = new noflo.Component();
c.inPorts.add('in1',
{ datatype: 'string' });
c.inPorts.add('in2', {
datatype: 'string',
addressable: true,
});
c.outPorts.add('out',
{ datatype: 'string' });
c.forwardBrackets = { in1: ['out'] };
c.process((input, output) => {
if (!input.hasData('in1', ['in2', 0], ['in2', 1])) { return; }
const first = input.getData('in1');
const second0 = input.getData(['in2', 0]);
const second1 = input.getData(['in2', 1]);
output.sendDone({ out: `1${first}:2${second0}:2${second1}:${c.nodeId}` });
});
return c;
};
before(() => {
loader = new noflo.ComponentLoader(baseDir);
return loader.listComponents()
.then(() => {
loader.registerComponent('process', 'Async', processAsync);
loader.registerComponent('process', 'Merge', processMerge);
loader.registerComponent('process', 'MergeA', processMergeA);
loader.registerComponent('process', 'Unscope', processUnscope);
loader.registerComponent('process', 'MergeUnscoped', processMergeUnscoped);
});
});
describe('pure Process API merging two inputs', () => {
let c = null;
let in1 = null;
let in2 = null;
let out = null;
before(() => {
const fbpData = 'INPORT=Pc1.IN:IN1\n'
+ 'INPORT=Pc2.IN:IN2\n'
+ 'OUTPORT=PcMerge.OUT:OUT\n'
+ 'Pc1(process/Async) OUT -> IN1 PcMerge(process/Merge)\n'
+ 'Pc2(process/Async) OUT -> IN2 PcMerge(process/Merge)';
return noflo.graph.loadFBP(fbpData)
.then((g) => {
loader.registerComponent('scope', 'Merge', g);
return loader.load('scope/Merge');
})
.then((instance) => {
c = instance;
in1 = noflo.internalSocket.createSocket();
c.inPorts.in1.attach(in1);
in2 = noflo.internalSocket.createSocket();
c.inPorts.in2.attach(in2);
return c.start();
});
});
beforeEach(() => {
out = noflo.internalSocket.createSocket();
c.outPorts.out.attach(out);
});
afterEach(() => {
c.outPorts.out.detach(out);
out = null;
});
it('should forward new-style brackets as expected', (done) => {
const expected = [
'CONN',
'< 1',
'< a',
'DATA 1bazPc1:2fooPc2:PcMerge',
'>',
'>',
'DISC',
];
const received = [];
out.on('connect', () => {
received.push('CONN');
});
out.on('begingroup', (group) => {
received.push(`< ${group}`);
});
out.on('data', (data) => {
received.push(`DATA ${data}`);
});
out.on('endgroup', () => {
received.push('>');
});
out.on('disconnect', () => {
received.push('DISC');
chai.expect(received).to.eql(expected);
done();
});
in2.connect();
in2.send('foo');
in2.disconnect();
in1.connect();
in1.beginGroup(1);
in1.beginGroup('a');
in1.send('baz');
in1.endGroup();
in1.endGroup();
in1.disconnect();
});
it('should forward new-style brackets as expected regardless of sending order', (done) => {
const expected = [
'CONN',
'< 1',
'< a',
'DATA 1bazPc1:2fooPc2:PcMerge',
'>',
'>',
'DISC',
];
const received = [];
out.on('connect', () => {
received.push('CONN');
});
out.on('begingroup', (group) => {
received.push(`< ${group}`);
});
out.on('data', (data) => {
received.push(`DATA ${data}`);
});
out.on('endgroup', () => {
received.push('>');
});
out.on('disconnect', () => {
received.push('DISC');
chai.expect(received).to.eql(expected);
done();
});
in1.connect();
in1.beginGroup(1);
in1.beginGroup('a');
in1.send('baz');
in1.endGroup();
in1.endGroup();
in1.disconnect();
in2.connect();
in2.send('foo');
in2.disconnect();
});
it('should forward scopes as expected', (done) => {
const expected = [
'x < 1',
'x DATA 1onePc1:2twoPc2:PcMerge',
'x >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in2.post(new noflo.IP('data', 'two',
{ scope: 'x' }));
in1.post(new noflo.IP('openBracket', 1,
{ scope: 'x' }));
in1.post(new noflo.IP('data', 'one',
{ scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1,
{ scope: 'x' }));
});
it('should not forward when scopes don\'t match', (done) => {
out.on('ip', (ip) => {
throw new Error(`Received unexpected ${ip.type} packet`);
});
c.network.once('end', () => {
done();
});
in2.post(new noflo.IP('data', 'two', { scope: 2 }));
in1.post(new noflo.IP('openBracket', 1, { scope: 1 }));
in1.post(new noflo.IP('data', 'one', { scope: 1 }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 1 }));
});
});
describe('Process API with IIPs and scopes', () => {
let c = null;
let in1 = null;
let out = null;
before(() => {
const fbpData = 'INPORT=Pc1.IN:IN1\n'
+ 'OUTPORT=PcMerge.OUT:OUT\n'
+ 'Pc1(process/Async) -> IN1 PcMerge(process/Merge)\n'
+ '\'twoIIP\' -> IN2 PcMerge(process/Merge)';
return noflo.graph.loadFBP(fbpData)
.then((g) => {
loader.registerComponent('scope', 'MergeIIP', g);
return loader.load('scope/MergeIIP');
})
.then((instance) => {
c = instance;
in1 = noflo.internalSocket.createSocket();
c.inPorts.in1.attach(in1);
return c.start();
});
});
beforeEach(() => {
out = noflo.internalSocket.createSocket();
c.outPorts.out.attach(out);
});
afterEach(() => {
c.outPorts.out.detach(out);
out = null;
});
it('should forward scopes as expected', (done) => {
const expected = [
'x < 1',
'x DATA 1onePc1:2twoIIP:PcMerge',
'x >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
});
});
describe('Process API with unscoped inport and scopes', () => {
let c = null;
let in1 = null;
let in2 = null;
let out = null;
before((done) => {
const fbpData = 'INPORT=Pc1.IN:IN1\n'
+ 'INPORT=Pc2.IN:IN2\n'
+ 'OUTPORT=PcMerge.OUT:OUT\n'
+ 'Pc1(process/Async) -> IN1 PcMerge(process/MergeUnscoped)\n'
+ 'Pc2(process/Async) -> IN2 PcMerge(process/MergeUnscoped)';
noflo.graph.loadFBP(fbpData, (err, g) => {
if (err) {
done(err);
return;
}
loader.registerComponent('scope', 'MergeUnscoped', g);
loader.load('scope/MergeUnscoped', (err, instance) => {
if (err) {
done(err);
return;
}
c = instance;
in1 = noflo.internalSocket.createSocket();
c.inPorts.in1.attach(in1);
in2 = noflo.internalSocket.createSocket();
c.inPorts.in2.attach(in2);
c.setUp(done);
});
});
});
beforeEach(() => {
out = noflo.internalSocket.createSocket();
c.outPorts.out.attach(out);
});
afterEach(() => {
c.outPorts.out.detach(out);
out = null;
});
it('should forward scopes as expected', (done) => {
const expected = [
'x < 1',
'x DATA 1onePc1:2twoPc2:PcMerge',
'x >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
in2.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in2.post(new noflo.IP('data', 'two', { scope: 'x' }));
in2.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
});
it('should forward packets without scopes', (done) => {
const expected = [
'null < 1',
'null DATA 1onePc1:2twoPc2:PcMerge',
'null >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1));
in1.post(new noflo.IP('data', 'one'));
in1.post(new noflo.IP('closeBracket'));
in2.post(new noflo.IP('openBracket', 1));
in2.post(new noflo.IP('data', 'two'));
in2.post(new noflo.IP('closeBracket', 1));
});
it('should forward scopes also on unscoped packet', (done) => {
const expected = [
'x < 1',
'x DATA 1onePc1:2twoPc2:PcMerge',
'x >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in2.post(new noflo.IP('openBracket', 1));
in2.post(new noflo.IP('data', 'two'));
in2.post(new noflo.IP('closeBracket', 1));
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
});
});
describe('Process API with unscoped outport and scopes', () => {
let c = null;
let in1 = null;
let in2 = null;
let out = null;
before((done) => {
const fbpData = 'INPORT=Pc1.IN:IN1\n'
+ 'INPORT=Pc2.IN:IN2\n'
+ 'OUTPORT=PcMerge.OUT:OUT\n'
+ 'Pc1(process/Unscope) -> IN1 PcMerge(process/Merge)\n'
+ 'Pc2(process/Unscope) -> IN2 PcMerge';
noflo.graph.loadFBP(fbpData, (err, g) => {
if (err) {
done(err);
return;
}
loader.registerComponent('scope', 'MergeUnscopedOut', g);
loader.load('scope/MergeUnscopedOut', (err, instance) => {
if (err) {
done(err);
return;
}
c = instance;
in1 = noflo.internalSocket.createSocket();
c.inPorts.in1.attach(in1);
in2 = noflo.internalSocket.createSocket();
c.inPorts.in2.attach(in2);
c.setUp(done);
});
});
});
beforeEach(() => {
out = noflo.internalSocket.createSocket();
c.outPorts.out.attach(out);
});
afterEach(() => {
c.outPorts.out.detach(out);
out = null;
});
it('should remove scopes as expected', (done) => {
const expected = [
'null < 1',
'null DATA 1onePc1:2twoPc2:PcMerge',
'null >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
in2.post(new noflo.IP('openBracket', 1, { scope: 'y' }));
in2.post(new noflo.IP('data', 'two', { scope: 'y' }));
in2.post(new noflo.IP('closeBracket', 1, { scope: 'y' }));
});
it('should forward packets without scopes', (done) => {
const expected = [
'null < 1',
'null DATA 1onePc1:2twoPc2:PcMerge',
'null >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1));
in1.post(new noflo.IP('data', 'one'));
in1.post(new noflo.IP('closeBracket'));
in2.post(new noflo.IP('openBracket', 1));
in2.post(new noflo.IP('data', 'two'));
in2.post(new noflo.IP('closeBracket', 1));
});
it('should remove scopes also on unscoped packet', (done) => {
const expected = [
'null < 1',
'null DATA 1onePc1:2twoPc2:PcMerge',
'null >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
in2.post(new noflo.IP('openBracket', 1));
in2.post(new noflo.IP('data', 'two'));
in2.post(new noflo.IP('closeBracket', 1));
});
});
describe('Process API with IIPs to addressable ports and scopes', () => {
let c = null;
let in1 = null;
let out = null;
before((done) => {
const fbpData = 'INPORT=Pc1.IN:IN1\n'
+ 'OUTPORT=PcMergeA.OUT:OUT\n'
+ 'Pc1(process/Async) -> IN1 PcMergeA(process/MergeA)\n'
+ '\'twoIIP0\' -> IN2[0] PcMergeA\n'
+ '\'twoIIP1\' -> IN2[1] PcMergeA';
noflo.graph.loadFBP(fbpData, (err, g) => {
if (err) {
done(err);
return;
}
loader.registerComponent('scope', 'MergeIIPA', g);
loader.load('scope/MergeIIPA', (err, instance) => {
if (err) {
done(err);
return;
}
c = instance;
in1 = noflo.internalSocket.createSocket();
c.inPorts.in1.attach(in1);
c.setUp(done);
});
});
});
beforeEach(() => {
out = noflo.internalSocket.createSocket();
c.outPorts.out.attach(out);
});
afterEach(() => {
c.outPorts.out.detach(out);
out = null;
});
it('should forward scopes as expected', (done) => {
const expected = [
'x < 1',
'x DATA 1onePc1:2twoIIP0:2twoIIP1:PcMergeA',
'x >',
];
const received = [];
const brackets = [];
out.on('ip', (ip) => {
switch (ip.type) {
case 'openBracket':
received.push(`${ip.scope} < ${ip.data}`);
brackets.push(ip.data);
break;
case 'data':
received.push(`${ip.scope} DATA ${ip.data}`);
break;
case 'closeBracket':
received.push(`${ip.scope} >`);
brackets.pop();
if (brackets.length) { return; }
chai.expect(received).to.eql(expected);
done();
break;
}
});
in1.post(new noflo.IP('openBracket', 1, { scope: 'x' }));
in1.post(new noflo.IP('data', 'one', { scope: 'x' }));
in1.post(new noflo.IP('closeBracket', 1, { scope: 'x' }));
});
});
});