Adds many more static workflow parser tests (#30)

This commit is contained in:
Riley Bauer 2018-11-03 15:23:37 -07:00 committed by k8s-ci-robot
parent e47c1ddb80
commit c879e9b78f
2 changed files with 422 additions and 55 deletions

View File

@ -1,5 +1,5 @@
{
"name": "seira-frontend",
"name": "pipelines-frontend",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
@ -594,15 +594,6 @@
"@types/react-router": "4.0.31"
}
},
"@types/react-swipeable-views": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.12.2.tgz",
"integrity": "sha512-c+OFdmEMUtdGeADR7OmnIUTNoejJTBjO64vBPFkdIpKiABS+DXtUHCzM34U/+Wy7FGeLoPHpKGnm2tcMMst/LA==",
"dev": true,
"requires": {
"@types/react": "16.4.14"
}
},
"@types/react-test-renderer": {
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.0.2.tgz",
@ -11354,41 +11345,6 @@
"whatwg-fetch": "2.0.3"
}
},
"react-swipeable-views": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.0.tgz",
"integrity": "sha512-r6H8lbtcI99oKykpLxYrI6O9im1lJ4D5/hf8bkNeQLdHZ9ftxS03qgEtguy3GpT5VB9yS4gErYWeaTrhCrysEg==",
"requires": {
"@babel/runtime": "7.0.0",
"dom-helpers": "3.3.1",
"prop-types": "15.6.2",
"react-swipeable-views-core": "0.13.0",
"react-swipeable-views-utils": "0.13.0",
"warning": "4.0.2"
}
},
"react-swipeable-views-core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.0.tgz",
"integrity": "sha512-MAe119eSN4obiqsIp+qoUWtLbyjz+dWEfz+qPurPvyIFoXxuxpBnsDy36+C7cBaCi5z4dRmfoMlm1dBAdIzvig==",
"requires": {
"@babel/runtime": "7.0.0",
"warning": "4.0.2"
}
},
"react-swipeable-views-utils": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.0.tgz",
"integrity": "sha512-1I4BhDqA6qkRdW0nexnudh/QdvVAVy0a7M5OyU2TrjaTovg6ufBouzqfqjZfUZUxVdOftTkPtisHmcqqZ+b1TA==",
"requires": {
"@babel/runtime": "7.0.0",
"fbjs": "0.8.17",
"keycode": "2.2.0",
"prop-types": "15.6.2",
"react-event-listener": "0.6.3",
"react-swipeable-views-core": "0.13.0"
}
},
"react-test-renderer": {
"version": "16.5.2",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.5.2.tgz",

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createGraph } from './StaticGraphParser';
import { createGraph, getNodeInfo } from './StaticGraphParser';
describe('StaticGraphParser', () => {
@ -24,21 +24,66 @@ describe('StaticGraphParser', () => {
entrypoint: 'template-1',
templates: [
{
dag: {
tasks: [
{
name: 'task-1',
template: 'task-1',
},
],
},
dag: { tasks: [{ name: 'task-1', template: 'task-1', },], },
name: 'template-1',
},
{
container: {},
name: 'container-1',
},
],
},
};
}
// In this pipeline, the conditionals are the templates: 'condition-1' and 'condition-2'
// 'condition-1-child' and 'condition-2-child' are not displayed in the static graph, but they
// are used by the parser to properly connect the nodes.
function newConditionalWorkflow(): any {
return {
spec: {
entrypoint: 'pipeline-flip-coin',
templates: [
{
name: 'condition-1',
steps: [[{
name: 'condition-1-child',
template: 'condition-1-child',
when: '{{inputs.parameters.flip-output}} == heads'
}]]
},
{ dag: { tasks: [{ name: 'heads', template: 'heads' }] }, name: 'condition-1-child' },
{
name: 'condition-2',
steps: [[{
name: 'condition-2-child',
template: 'condition-2-child',
when: '{{inputs.parameters.flip-output}} == tails'
}]]
},
{ dag: { tasks: [{ name: 'tails', template: 'tails' }] }, name: 'condition-2-child' },
{
dag: {
tasks: [
{ dependencies: ['flip'], name: 'condition-1', template: 'condition-1' },
{ dependencies: ['flip'], name: 'condition-2', template: 'condition-2' },
{ name: 'flip', template: 'flip' }
]
},
name: 'pipeline-flip-coin'
},
{
container: { args: [ /* omitted */], command: ['sh', '-c'], },
name: 'flip',
outputs: { parameters: [{ name: 'flip-output', valueFrom: { path: '/tmp/output' } }] }
},
{ container: { command: ['echo', '\'heads\''], }, name: 'heads', },
{ container: { command: ['echo', '\'tails\''], }, name: 'tails', },
]
}
};
}
describe('createGraph', () => {
it('Creates a single node with no edges for a workflow with one step.', () => {
const workflow = newWorkflow();
@ -79,6 +124,56 @@ describe('StaticGraphParser', () => {
expect(g.edges()[0].w).toBe('task-2');
});
it('Shows conditional nodes without adding conditional children as nodes', () => {
const g = createGraph(newConditionalWorkflow());
expect(g.nodeCount()).toBe(5);
['flip', 'condition-1', 'condition-2', 'heads', 'tails'].forEach((nodeName) => {
expect(g.nodes()).toContain(nodeName);
});
});
it('Connects conditional graph correctly', () => {
const g = createGraph(newConditionalWorkflow());
// 'flip' has two possible outcomes: 'condition-1' and 'condition-2'
expect(g.edges()[0].v).toBe('flip');
expect(g.edges()[0].w).toBe('condition-1');
expect(g.edges()[1].v).toBe('flip');
expect(g.edges()[1].w).toBe('condition-2');
// condition-1 means the 'heads' node will run
expect(g.edges()[2].v).toBe('condition-1');
expect(g.edges()[2].w).toBe('heads');
// condition-2 means the 'tails' node will run
expect(g.edges()[3].v).toBe('condition-2');
expect(g.edges()[3].w).toBe('tails');
// Confirm there are no other nodes or edges
expect(g.nodeCount()).toBe(5);
expect(g.edgeCount()).toBe(4);
});
it('Finds conditionals and colors them', () => {
const g = createGraph(newConditionalWorkflow());
g.nodes().forEach((nodeName) => {
const node = g.node(nodeName);
if (nodeName.startsWith('condition')) {
expect(node.bgColor).toBe('cornsilk');
} else {
expect(node.bgColor).toBeUndefined();
}
});
});
it('Renders extremely simple workflows with no steps or DAGs', () => {
const simpleWorkflow = {
spec: {
entrypoint: 'template-1',
templates: [ { container: {}, name: 'template-1', }, ],
},
} as any;
const g = createGraph(simpleWorkflow);
expect(g.nodeCount()).toBe(1);
expect(g.edgeCount()).toBe(0);
});
// This test covers the way that compiled Pipelines handle DSL-defined exit-handlers.
// These exit-handlers are different from Argo's 'onExit'.
// The compiled Pipelines will contain 1 DAG for the entry point, that has a single task which
@ -129,7 +224,7 @@ describe('StaticGraphParser', () => {
expect(() => createGraph(workflow)).toThrowError('Pipeline had template with multiple steps.');
});
it('Returns an error message if the workflow spec has a template with multiple steps', () => {
it('Throws an error message if the workflow spec has a template with multiple steps', () => {
const workflow = {
spec: {
templates: [
@ -147,4 +242,320 @@ describe('StaticGraphParser', () => {
expect(() => createGraph(workflow)).toThrowError('Pipeline had template with multiple steps');
});
});
describe('getNodeInfo', () => {
it('Returns nodeInfo containing only \'unknown\' nodeType if nodeId is undefined', () => {
const nodeInfo = getNodeInfo(newWorkflow(), undefined);
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if nodeId is empty', () => {
const nodeInfo = getNodeInfo(newWorkflow(), '');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if workflow is undefined', () => {
const nodeInfo = getNodeInfo(undefined, 'someId');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if workflow.spec is undefined', () => {
const workflow = newWorkflow();
workflow.spec = undefined;
const nodeInfo = getNodeInfo(workflow, 'someId');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if workflow.spec.templates is undefined', () => {
const workflow = newWorkflow();
workflow.spec.templates = undefined;
const nodeInfo = getNodeInfo(workflow, 'someId');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if no template matches provided ID', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'id-not-in-spec');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if template matching provided ID has no container or steps', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
// No container or steps here
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if template matching provided ID has no container and empty steps', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
// No container here
name: 'template-1',
steps: [],
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing only \'unknown\' nodeType if template matching provided ID has no container and array of empty steps', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
// No container here
name: 'template-1',
steps: [[]],
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo).toEqual({ nodeType: 'unknown' });
});
it('Returns nodeInfo containing container args', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {
args: ['arg1', 'arg2'],
},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.args).toEqual(['arg1', 'arg2']);
});
it('Returns nodeInfo containing container commands', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {
command: ['command1', 'command2']
},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.command).toEqual(['command1', 'command2']);
});
it('Returns nodeInfo containing container image name', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {
image: 'someImageID'
},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.image).toBe('someImageID');
});
it('Returns nodeInfo containing container inputs as list of name/value duples', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {},
inputs: {
parameters: [
{ name: 'param1', value: 'val1' },
{ name: 'param2', value: 'val2' },
],
},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.inputs).toEqual([['param1', 'val1'], ['param2', 'val2']]);
});
it('Returns empty strings for inputs with no specified value', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {},
inputs: {
parameters: [
{ name: 'param1' },
{ name: 'param2' },
],
},
name: 'template-1',
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.inputs).toEqual([['param1', ''], ['param2', '']]);
});
it('Returns nodeInfo containing container outputs as list of name/value duples, pulling from valueFrom if necessary', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {},
name: 'template-1',
outputs: {
parameters: [
{ name: 'param1', value: 'val1' },
{ name: 'param2', valueFrom: { jsonPath: 'jsonPath' } },
{ name: 'param3', valueFrom: { path: 'path' } },
{ name: 'param4', valueFrom: { parameter: 'parameterReference' } },
{ name: 'param5', valueFrom: { jqFilter: 'jqFilter' } },
],
},
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.outputs).toEqual([
['param1', 'val1'],
['param2', 'jsonPath'],
['param3', 'path'],
['param4', 'parameterReference'],
['param5', 'jqFilter'],
]);
});
it('Returns empty strings for outputs with no specified value', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
container: {},
name: 'template-1',
outputs: {
parameters: [
{ name: 'param1' },
{ name: 'param2' },
],
},
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('container');
expect(nodeInfo.containerInfo!.outputs).toEqual([['param1', ''], ['param2', '']]);
});
it('Returns nodeType \'steps\' when node template has steps', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
name: 'template-1',
steps: [[
'something',
]]
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('steps');
expect(nodeInfo.stepsInfo).toEqual({ conditional: '', parameters: [[]]);
});
it('Returns nodeInfo with step template\'s conditional when node template has \'when\' property', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
name: 'template-1',
steps: [[ { when: '{{someVar}} == something' } ]]
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('steps');
expect(nodeInfo.stepsInfo).toEqual({ conditional: '{{someVar}} == something', parameters: [[]]);
});
it('Returns nodeInfo with step template\'s arguments when node template has \'when\' property', () => {
const workflow = {
spec: {
entrypoint: 'template-1',
templates: [
{
name: 'template-1',
steps: [[{
arguments: {
parameters: [
{ name: 'param1', value: 'val1' },
{ name: 'param2', value: 'val2' },
],
},
}]]
},
],
},
} as any;
const nodeInfo = getNodeInfo(workflow, 'template-1');
expect(nodeInfo.nodeType).toBe('steps');
expect(nodeInfo.stepsInfo)
.toEqual({ conditional: '', parameters: [['param1', 'val1'], ['param2', 'val2']]);
});
});
});