/* * Copyright 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.xd.dirt.job.dsl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.junit.Ignore; import org.junit.Test; /** * Parse job specifications and verify either the correct abstract syntax tree is produced or the current exception comes out. * The parser does no semantic validation, it is purely syntax checking. * * @author Andy Clement */ public class JobParserTests { private JobSpecification js; @Test public void jobTokenization() { Tokens tokens = new Tokens("jobA | BROKEN = $END |'*'=something"); assertToken(TokenKind.IDENTIFIER, "jobA", 0, 4, tokens.next()); assertToken(TokenKind.PIPE, "|", 5, 6, tokens.next()); assertToken(TokenKind.IDENTIFIER, "BROKEN", 7, 13, tokens.next()); assertToken(TokenKind.EQUALS, "=", 14, 15, tokens.next()); assertToken(TokenKind.IDENTIFIER, "$END", 16, 20, tokens.next()); assertToken(TokenKind.PIPE, "|", 21, 22, tokens.next()); assertToken(TokenKind.LITERAL_STRING, "'*'", 22, 25, tokens.next()); assertToken(TokenKind.EQUALS, "=", 25, 26, tokens.next()); assertToken(TokenKind.IDENTIFIER, "something", 26, 35, tokens.next()); } @Test public void rogueTokenization() { Tokens tokens = new Tokens("<(xx | '*'=$END) & yy>"); assertToken(TokenKind.SPLIT_OPEN, "<", 0, 1, tokens.next()); assertToken(TokenKind.OPEN_PAREN, "(", 1, 2, tokens.next()); assertToken(TokenKind.IDENTIFIER, "xx", 2, 4, tokens.next()); assertToken(TokenKind.PIPE, "|", 5, 6, tokens.next()); assertToken(TokenKind.LITERAL_STRING, "'*'", 7, 10, tokens.next()); assertToken(TokenKind.EQUALS, "=", 10, 11, tokens.next()); assertToken(TokenKind.IDENTIFIER, "$END", 11, 15, tokens.next()); assertToken(TokenKind.CLOSE_PAREN, ")", 15, 16, tokens.next()); assertToken(TokenKind.AMPERSAND, "&", 17, 18, tokens.next()); assertToken(TokenKind.IDENTIFIER, "yy", 19, 21, tokens.next()); assertToken(TokenKind.SPLIT_CLOSE, ">", 21, 22, tokens.next()); } @Test public void rogueParsing() { js = parse("<(xx | '*'=$END) & yy>"); assertEquals("<xx | '*' = $END & yy>", js.stringify()); } @Test public void jobTokenization2() { Tokens tokens = new Tokens("jobA|BROKEN=$END|'*'=something"); assertToken(TokenKind.IDENTIFIER, "jobA", 0, 4, tokens.next()); assertToken(TokenKind.PIPE, "|", 4, 5, tokens.next()); assertToken(TokenKind.IDENTIFIER, "BROKEN", 5, 11, tokens.next()); assertToken(TokenKind.EQUALS, "=", 11, 12, tokens.next()); assertToken(TokenKind.IDENTIFIER, "$END", 12, 16, tokens.next()); assertToken(TokenKind.PIPE, "|", 16, 17, tokens.next()); assertToken(TokenKind.LITERAL_STRING, "'*'", 17, 20, tokens.next()); assertToken(TokenKind.EQUALS, "=", 20, 21, tokens.next()); assertToken(TokenKind.IDENTIFIER, "something", 21, 30, tokens.next()); } @Test public void globalOptions() { js = parse("foo --timeout=100 --pollInterval=1000"); assertEquals("foo --timeout=100 --pollInterval=1000", js.stringify()); Map<String, String> options = js.getGlobalOptionsMap(); assertTrue(options.containsKey("timeout")); assertEquals(Integer.toString(100), options.get("timeout")); assertTrue(options.containsKey("pollInterval")); assertEquals(Integer.toString(1000), options.get("pollInterval")); assertEquals(2, options.size()); } @Test public void graphToText() { checkDSLToGraphAndBackToDSL("foojob"); checkDSLToGraphAndBackToDSL("<aa | xx = foo & bb>"); } @Test public void graphToTextWithAllExitsCoveredNode() { // checkDSLToGraphAndBackToDSL("aaa | FOO = XXX | B = bbb | '*' = ccc || bbb || ccc"); js = parse("aaa | FOO = XXX | B = bbb | '*' = ccc || bbb || ccc"); Graph g = js.toGraph(); System.out.println(g); assertEquals("aaa | FOO = XXX | B = bbb | '*' = ccc || bbb || ccc", g.toDSLText()); // checkDSLToGraphAndBackToDSL("aaa | '*' = ccc | S1 = bbb || bbb || ccc"); } @Test public void graphToTextFlow() { checkDSLToGraphAndBackToDSL("foojob || barjob"); } @Test public void globalOptionsGraphConversion() { checkDSLToGraphAndBackToDSL("foojob || barjob --foo=bar --goo=boo"); } @Test public void testMissingPunctuation() { // If JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS is set to true, this test will need another branch // that deals with that since 'eee fff' is a valid inline job spec checkForParseError("<aaa & bbb & ccc> || <ddd & eee fff>", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 32, "fff"); } @Test public void referenceWithOptions() { js = parse("jobModuleA --foo=bar --boo=gar"); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobReference jr = (JobReference) jn; assertTrue(jr.isReference()); assertFalse(jr.isDefinition()); assertEquals("jobModuleA", jr.getName()); ArgumentNode[] args = jr.getArguments(); assertNull(args); assertEquals(2, js.getGlobalOptionsMap().size()); } @Test public void referenceWithOptionsInTransition() { checkForParseError("aaa || bbb | d = foo --aaa=bbb || ccc", JobDSLMessage.UNEXPECTED_DATA_AFTER_JOBSPEC, 31, "||"); } @Test public void graphToTextSplit() { checkDSLToGraphAndBackToDSL("<foojob & barjob>"); } @Test public void errorMissingJob() { // Only single & between jobs checkForParseError("<aa && bb>", JobDSLMessage.EXPECTED_JOB_REF_OR_DEF, 5); } @Test public void errorMissingJob2() { // Need a job in between all those things checkForParseError("aa |||| bb", JobDSLMessage.EXPECTED_JOB_REF_OR_DEF, 6); } @Test public void errorMissingJob3() { // Need a job in between all those things checkForParseError("aa ||| bb", JobDSLMessage.EXPECTED_JOB_REF_OR_DEF, 6); } @Test public void graphToTextFlowWithTransition() { checkDSLToGraphAndBackToDSL("foojob | completed = killjob || barjob"); } @Test public void graphToTextSplitWithTransition() { checkDSLToGraphAndBackToDSL("<foojob | completed = killjob & barjob>"); } @Test public void graphToTextSplitWithTransition2() { checkDSLToGraphAndBackToDSL("<foojob | completed = killjob & barjob | completed = killjob>"); } @Test public void graphToTextComplex() { checkDSLToGraphAndBackToDSL("<foojob & bbb || ccc>"); } @Ignore @Test public void toDSLTextTransitions() { js = parse("aaa | '*' = $END || bbb"); assertEquals("", js.stringify()); // Unclear if this is valid, there isn't really any orchestration going on, what drives bbb? checkDSLToGraphAndBackToDSL("aaa | '*' = $END || bbb"); } @Ignore @Test // You can't draw this on the graph, it would end up looking like "aaa | '*' = $END || bbb || ccc public void toDSLTextTransitionsSplit() { checkDSLToGraphAndBackToDSL("aaa | '*' = $END || <bbb & ccc>"); } @Test public void toDSLTextTransitionsFlow() { checkDSLToGraphAndBackToDSL("aaa | '*' = $END || bbb || ccc"); } @Test public void toDSLTextSplitToFlow() { checkDSLToGraphAndBackToDSL("<a & b> || foo"); } @Test public void toDSLTextWithPropertiesOnDefinition() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { checkDSLToGraphAndBackToDSL("<a x --aaa=bbb & b> || foo"); checkDSLToGraphAndBackToDSL("<a & b y --aaa=bbb --ccc=ddd> || foo"); } else { checkForParseError("<a x --aaa=bbb & b> || foo", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 3, "x"); checkForParseError("<a & b y --aaa=bbb --ccc=ddd> || foo", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 7, "y"); } } @Test public void toDSLTextWithGlobalOptions() { checkDSLToGraphAndBackToDSL("<a & b> || foo --aaa=bbb"); checkDSLToGraphAndBackToDSL("<a & b> || foo --aaa=bbb --ccc=ddd"); } @Test public void toDSLTextFlowToSplit() { checkDSLToGraphAndBackToDSL("foo || <c & d>"); } @Test public void toDSLTextSplitFlowSplit() { checkDSLToGraphAndBackToDSL("<a & b> || foo || <c & d>"); checkDSLToGraphAndBackToDSL("<a & b> || foo | wibble = $END || <c & d>"); } @Test public void toDSLTextFlowTransitions() { checkDSLToGraphAndBackToDSL("aaa | COMPLETED = kill || bbb || ccc"); checkDSLToGraphAndBackToDSL("aaa | COMPLETED = kill || bbb | COMPLETED = kill || ccc"); checkDSLToGraphAndBackToDSL("aaa | COMPLETED = kill | FOO = bar || bbb | COMPLETED = kill || ccc"); } @Test public void toDSLTextSplitTransitions() { checkDSLToGraphAndBackToDSL("<aaa | COMPLETED = kill & bbb> || ccc"); checkDSLToGraphAndBackToDSL("<aaa | COMPLETED = kill & bbb | COMPLETED = kill> || ccc"); // checkDSLToGraphAndBackToDSL("<aaa | COMPLETED = kill | '*' = kill2 & bbb | COMPLETED = kill> || ccc"); } @Test public void toDSLTextNestedSplits() { checkDSLToGraphAndBackToDSL("<aaa & ccc & ddd> || eee"); checkDSLToGraphAndBackToDSL("<aaa & bbb || <ccc & ddd>> || eee"); checkDSLToGraphAndBackToDSL("<aaa || <bbb & ccc> || foo & ddd || eee> || fff"); checkDSLToGraphAndBackToDSL("<aaa || <bbb & ccc> & ddd || eee> || fff"); checkDSLToGraphAndBackToDSL("<aaa || <bbb & ccc> & ddd || eee> || fff"); checkDSLToGraphAndBackToDSL("<aaa & bbb || <ccc & ddd>> || <eee & fff>"); checkDSLToGraphAndBackToDSL("<aaa & bbb || <ccc & ddd>> || <eee & fff> || <ggg & hhh>"); } @Test public void toDSLTextLong() { checkDSLToGraphAndBackToDSL( "<aaa || fff & bbb || ggg || <ccc & ddd>> || eee || hhh || iii || <jjj & kkk || lll>"); } @Test public void toDSLTextSync() { String spec = "<a & b> || <c & d>"; checkDSLToGraphAndBackToDSL(spec); } @Test public void toDSLTextSqoop() { String spec = "<(sqoop-6e44 | 'FAILED' = kill1" + " || sqoop-e07a | 'FAILED' = kill1) & " + " (sqoop-035f | 'FAILED' = kill2" + " || sqoop-9408 | 'FAILED' = kill2" + " || sqoop-a6e0 | 'FAILED' = kill2" + " || sqoop-e522 | 'FAILED' = kill2" + " || shell-b521 | 'FAILED' = kill2) & " + " (sqoop-6420 | 'FAILED' = kill3)>"; // DSL text right now doesn't include parentheses (they aren't necessary) spec = "<sqoop-6e44 | 'FAILED' = kill1" + " || sqoop-e07a | 'FAILED' = kill1 &" + " sqoop-035f | 'FAILED' = kill2" + " || sqoop-9408 | 'FAILED' = kill2" + " || sqoop-a6e0 | 'FAILED' = kill2" + " || sqoop-e522 | 'FAILED' = kill2" + " || shell-b521 | 'FAILED' = kill2 &" + " sqoop-6420 | 'FAILED' = kill3>"; checkDSLToGraphAndBackToDSL(spec); } @Test public void toDSLTextManualSync() { // Here foo is effectively acting as a sync node String spec = "<a & b> || foo || <c & d>"; checkDSLToGraphAndBackToDSL(spec); } @Test public void oneJobReference() { js = parse("foojob"); // Basic data: assertEquals("foojob", js.getJobDefinitionText()); assertEquals(0, js.getStartPos()); assertEquals(6, js.getEndPos()); // The job itself: assertEquals("foojob", js.stringify()); JobNode jn = js.getJobNode(); assertFalse(jn.isSplit()); assertFalse(jn.isFlow()); JobReference jd = (JobReference) jn; assertTrue(jd.isReference()); assertEquals("foojob", jd.getName()); } @Test public void whitespace() { js = parse("A||B"); assertEquals("A || B", js.stringify()); js = parse("<A&B>"); assertEquals("<A & B>", js.stringify()); js = parse("<A||B&C>"); assertEquals("<A || B & C>", js.stringify()); } @Test public void endTransition() { js = parse("jobA | BROKEN=$END"); assertEquals("jobA | BROKEN = $END", js.stringify()); js = parse("jobA|BROKEN=$END"); assertEquals("jobA | BROKEN = $END", js.stringify()); js = parse("jobA |BROKEN =$END"); assertEquals("jobA | BROKEN = $END", js.stringify()); js = parse("jobA |BROKEN= $END"); assertEquals("jobA | BROKEN = $END", js.stringify()); } @Test public void failTransition() { js = parse("jobA | BROKEN=$FAIL"); assertEquals("jobA | BROKEN = $FAIL", js.stringify()); js = parse("jobA|BROKEN=$FAIL"); assertEquals("jobA | BROKEN = $FAIL", js.stringify()); js = parse("jobA |BROKEN =$FAIL"); assertEquals("jobA | BROKEN = $FAIL", js.stringify()); js = parse("jobA |BROKEN= $FAIL"); assertEquals("jobA | BROKEN = $FAIL", js.stringify()); } @Test public void simpleJobSequence() { js = parse("jobA || jobB"); assertEquals("jobA || jobB", js.getJobDefinitionText()); assertEquals("jobA || jobB", js.stringify()); JobNode jn = js.getJobNode(); assertFalse(jn.isSplit()); assertTrue(jn.isFlow()); assertEquals(2, jn.getSeriesLength()); JobNode j1 = jn.getSeriesElement(0); JobNode j2 = jn.getSeriesElement(1); assertEquals("jobA[0>4]", j1.stringify(true)); assertEquals("jobB[8>12]", j2.stringify(true)); } @Test public void tripleJobSequence() { js = parse("jobA || jobB || jobC"); assertEquals("jobA || jobB || jobC", js.getJobDefinitionText()); assertEquals("jobA || jobB || jobC", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isFlow()); assertEquals(3, jn.getSeriesLength()); JobNode j1 = jn.getSeriesElement(0); JobNode j2 = jn.getSeriesElement(1); JobNode j3 = jn.getSeriesElement(2); assertEquals("jobA[0>4]", j1.stringify(true)); assertEquals("jobB[8>12]", j2.stringify(true)); assertEquals("jobC[16>20]", j3.stringify(true)); } @Test public void parentheses() { js = parse("jobA || (jobB || jobC)"); assertEquals("jobA || (jobB || jobC)", js.getJobDefinitionText()); assertEquals("jobA || jobB || jobC", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isFlow()); assertEquals(3, jn.getSeriesLength()); JobNode j1 = jn.getSeriesElement(0); JobNode j2 = jn.getSeriesElement(1); JobNode j3 = jn.getSeriesElement(2); assertEquals("jobA[0>4]", j1.stringify(true)); assertEquals("jobB[9>13]", j2.stringify(true)); assertEquals("jobC[17>21]", j3.stringify(true)); } @Test public void simpleParallelSequence() { js = parse("<jobA & jobB>"); assertEquals("<jobA & jobB>", js.getJobDefinitionText()); assertEquals("<jobA & jobB>", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isSplit()); assertEquals(2, jn.getSeriesLength()); JobNode j1 = jn.getSeriesElement(0); JobNode j2 = jn.getSeriesElement(1); assertEquals("jobA[1>5]", j1.stringify(true)); assertEquals("jobB[8>12]", j2.stringify(true)); } @Test public void tripleParallelSequence() { js = parse("<jobA & jobB & jobC>"); assertEquals("<jobA & jobB & jobC>", js.getJobDefinitionText()); assertEquals("<jobA & jobB & jobC>", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isSplit()); assertEquals(3, jn.getSeriesLength()); JobNode js1 = jn.getSeriesElement(0); JobNode js2 = jn.getSeriesElement(1); JobNode js3 = jn.getSeriesElement(2); assertEquals("jobA[1>5]", js1.stringify(true)); assertEquals("jobB[8>12]", js2.stringify(true)); assertEquals("jobC[15>19]", js3.stringify(true)); assertTrue(js1 instanceof JobReference); assertEquals("jobA", ((JobReference) js1).getName()); assertEquals("jobB", ((JobReference) js2).getName()); assertEquals("jobC", ((JobReference) js3).getName()); } @Test public void parentheses2() { js = parse("<(jobA || jobB || jobC) & jobC>"); assertEquals("<(jobA || jobB || jobC) & jobC>", js.getJobDefinitionText()); assertEquals("<jobA || jobB || jobC & jobC>", js.stringify()); } @Test public void simpleParallelAndSequential() { js = parse("<jobA || jobA2 & jobB || jobB2 & jobC || jobC2>"); assertEquals("<jobA || jobA2 & jobB || jobB2 & jobC || jobC2>", js.getJobDefinitionText()); assertEquals("<jobA || jobA2 & jobB || jobB2 & jobC || jobC2>", js.stringify()); JobNode jobNode = js.getJobNode(); assertTrue(jobNode instanceof Split); Split pjs = (Split) jobNode; assertEquals(3, pjs.getSeriesLength()); JobNode js1 = pjs.getSeriesElement(0); JobNode js2 = pjs.getSeriesElement(1); JobNode js3 = pjs.getSeriesElement(2); assertEquals("jobA[1>5] || jobA2[9>14]", js1.stringify(true)); assertEquals("jobB[17>21] || jobB2[25>30]", js2.stringify(true)); assertEquals("jobC[33>37] || jobC2[41>46]", js3.stringify(true)); assertEquals(2, js1.getSeriesLength()); assertEquals(2, js2.getSeriesLength()); assertEquals(2, js3.getSeriesLength()); } @Test public void funnyJobNames() { js = parse("a.b.c"); assertEquals("a.b.c", ((JobReference) js.getJobNode()).getName()); js = parse("a_b.c"); assertEquals("a_b.c", ((JobReference) js.getJobNode()).getName()); js = parse("a_b_c"); assertEquals("a_b_c", ((JobReference) js.getJobNode()).getName()); } @Test public void nestedSplit1() { js = parse("<<jobA & jobB> & jobC>"); assertEquals("<<jobA & jobB> & jobC>", js.getJobDefinitionText()); assertEquals("<<jobA & jobB> & jobC>", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isSplit()); Split pjs = (Split) jn; assertEquals(2, pjs.getSeriesLength()); JobNode j1 = pjs.getSeriesElement(0); assertTrue(j1.isSplit()); assertEquals(2, j1.getSeriesLength()); JobNode j2 = pjs.getSeriesElement(1); assertFalse(j2.isSplit()); } @Test public void nestedSplit2() { js = parse("<jobA & <jobB & jobC> & jobD>"); assertEquals("<jobA & <jobB & jobC> & jobD>", js.getJobDefinitionText()); assertEquals("<jobA & <jobB & jobC> & jobD>", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isSplit()); assertEquals(3, jn.getSeriesLength()); JobNode j1 = jn.getSeriesElement(0); assertTrue(j1.isJobDescriptor()); assertEquals("jobA", j1.stringify()); JobNode j2 = jn.getSeriesElement(1); assertTrue(j2.isSplit()); assertEquals(2, j2.getSeriesLength()); JobNode j3 = jn.getSeriesElement(2); assertTrue(j3.isJobDescriptor()); assertEquals("jobD", j3.stringify()); } @Test public void mixSplitSerial() { js = parse("<jobA & jobB || jobC & jobD>"); assertEquals("<jobA & jobB || jobC & jobD>", js.getJobDefinitionText()); assertEquals("<jobA & jobB || jobC & jobD>", js.stringify()); JobNode jn = js.getJobNode(); assertTrue(jn.isSplit()); assertEquals(3, jn.getSeriesLength()); JobNode ja = jn.getSeriesElement(0); assertTrue(ja.isJobDescriptor()); assertTrue(((JobDescriptor) ja).isReference()); assertEquals("jobA", ((JobReference) ja).getName()); JobNode jb = jn.getSeriesElement(1); assertTrue(jb.isFlow()); assertEquals("jobB", jb.getSeriesElement(0).stringify()); assertEquals("jobC", jb.getSeriesElement(1).stringify()); } @Test public void splitJoin() { js = parse("<jobA & jobB> || jobC"); assertEquals("<jobA & jobB> || jobC", js.getJobDefinitionText()); assertEquals("<jobA & jobB> || jobC", js.stringify()); } @Test public void inlineJobDefinition() { String dsl = "jobModuleA jobNameA"; if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse(dsl); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobDefinition jd = (JobDefinition) jn; assertFalse(jd.isReference()); assertTrue(jd.isDefinition()); assertEquals("jobModuleA", jd.getJobModuleName()); assertEquals("jobNameA", jd.getJobName()); } else { checkForParseError(dsl, JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 11, "jobNameA"); } } @Test public void singleTransition() { js = parse("foo | completed = bar"); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobDescriptor jd = (JobDescriptor) jn; List<Transition> transitions = jd.getTransitions(); assertEquals(1, transitions.size()); assertEquals("completed", transitions.get(0).getStateName()); assertEquals("bar", transitions.get(0).getTargetJobName()); } @Test public void doubleTransition() { js = parse("foo | completed = bar | wibble=wobble"); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobDescriptor jd = (JobDescriptor) jn; List<Transition> transitions = jd.getTransitions(); assertEquals(2, transitions.size()); assertEquals("completed", transitions.get(0).getStateName()); assertEquals("bar", transitions.get(0).getTargetJobName()); assertEquals("wibble", transitions.get(1).getStateName()); assertEquals("wobble", transitions.get(1).getTargetJobName()); } @Test public void inlineJobDefWithTransition() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("foo bar --aaa=bbb | completed = bar | wibble=wobble"); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobDescriptor jd = (JobDescriptor) jn; assertTrue(jd.isDefinition()); JobDefinition jobdef = (JobDefinition) jd; assertEquals("foo", jobdef.getJobModuleName()); assertEquals("bar", jobdef.getJobName()); ArgumentNode[] args = jobdef.getArguments(); assertEquals(1, args.length); assertEquals("aaa", args[0].getName()); assertEquals("bbb", args[0].getValue()); List<Transition> transitions = jd.getTransitions(); assertEquals(2, transitions.size()); assertEquals("completed", transitions.get(0).getStateName()); assertEquals("bar", transitions.get(0).getTargetJobName()); assertEquals("wibble", transitions.get(1).getStateName()); assertEquals("wobble", transitions.get(1).getTargetJobName()); } else { checkForParseError("foo bar --aaa=bbb | completed = bar | wibble=wobble", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "bar"); } } @Test public void wildcardTransition() { js = parse("foo | '*' = wibble"); assertEquals("foo | '*' = wibble", js.stringify()); js = parse("foo | \"*\" = wibble"); assertEquals("foo | \"*\" = wibble", js.stringify()); } @Test public void splitWithTransition() { js = parse("<foo | completed=kill & bar>"); assertEquals("<foo | completed=kill & bar>", js.getJobDefinitionText()); assertEquals("<foo | completed = kill & bar>", js.stringify()); } @Test public void splitWithTransitionLiterals() { js = parse("<foo | 'completed'=kill & bar>"); assertEquals("<foo | 'completed'=kill & bar>", js.getJobDefinitionText()); assertEquals("<foo | 'completed' = kill & bar>", js.stringify()); } @Test public void multiLine() { js = parse("<foo\n" + " | 'completed'=kill\n" + " | '*' = custard\n" + " & bar>"); assertEquals("<foo\n" + " | 'completed'=kill\n" + " | '*' = custard\n" + " & bar>", js.getJobDefinitionText()); assertEquals("<foo | 'completed' = kill | '*' = custard & bar>", js.stringify()); // assertEquals("<foo | 'completed' = kill | '*' = custard & bar>", js.format()); } @Test public void toGraphBlank() { js = parse(""); Graph g = js.toGraph(); assertEquals(toExpectedGraph("n:0:START,n:1:END,l:0:1"), g.toJSON()); js = parse(" "); g = js.toGraph(); assertEquals(toExpectedGraph("n:0:START,n:1:END,l:0:1"), g.toJSON()); } @Test public void toGraphSequence() { js = parse("foo || bar"); assertEquals(toExpectedGraph("n:0:START,n:1:foo,n:2:bar,n:3:END,l:0:1,l:1:2,l:2:3"), js.toGraph().toJSON()); } @Test public void complexToGraphAndBack() { js = parse( "aaa | 'FAILED' = iii || <bbb | 'FAILED' = iii | '*' = $END & ccc | 'FAILED'=jjj | '*'=$END> ||ddd| 'FAILED'=iii||eee| 'FAILED'=iii||fff| 'FAILED'=iii||<ggg| 'FAILED'=kkk | '*'=$END& hhh| 'FAILED'=kkk| '*'=$END> "); checkDSLToGraphAndBackToDSL( "aaa | 'FAILED' = iii || <bbb | 'FAILED' = iii | '*' = $END & ccc | 'FAILED' = jjj | '*' = $END> || ddd | 'FAILED' = iii || eee | 'FAILED' = iii || fff | 'FAILED' = iii || <ggg | 'FAILED' = kkk | '*' = $END & hhh | 'FAILED' = kkk | '*' = $END>"); } @Test public void toGraph$END() { js = parse("foo | oranges=$END"); // AST creation (and DSL printing from it) assertEquals("foo | oranges = $END", js.stringify()); // Graph creation // Note: node '2' does not exist, it was an $END node for the transition, when we discovered that was // going to be joined to the final $END node we re-routed the links targeting '2' to the real end // node and removed it assertEquals(toExpectedGraph("n:0:START,n:1:foo,n:3:END,l:0:1,l:1:3:transitionName=oranges"), js.toGraph().toJSON()); // Graph to DSL checkDSLToGraphAndBackToDSL("foo | oranges = $END"); // toXML assertEquals(loadXml("end1"), js.toXML("test1", true)); } @Test public void toGraph$END2() { js = parse("aaa | foo = $END | B = bbb | '*' = ccc || bbb || ccc"); // AST creation (and DSL printing from it) assertEquals("aaa | foo = $END | B = bbb | '*' = ccc || bbb || ccc", js.stringify()); // {"nodes":[{"id":"0","name":"START"},{"id":"1","name":"aaa"},{"id":"3","name":"bbb"},{"id":"4","name":"ccc"}, // {"id":"5","name":"END"}], // "links":[{"from":"0","to":"1"},{"from":"1","to":"5","properties":{"transitionName":"foo"}}, // {"from":"1","to":"3","properties":{"transitionName":"B"}}, // {"from":"1","to":"4","properties":{"transitionName":"'*'"}}, // {"from":"3","to":"4"},{"from":"4","to":"5"}]} // Graph creation assertEquals( toExpectedGraph("n:0:START,n:1:aaa,n:3:bbb,n:4:ccc,n:5:END," + "l:0:1,l:1:5:transitionName=foo,l:1:3:transitionName=B,l:1:4:transitionName='*',l:3:4,l:4:5"), js.toGraph().toJSON()); // Graph to DSL checkDSLToGraphAndBackToDSL("aaa | foo = $END | B = bbb | '*' = ccc || bbb || ccc"); // toXML assertEquals(loadXml("end2"), js.toXML("test1", true)); } @Test public void toGraph$END3() { // The trailing 'bbb' is redundant here... js = parse("aaa | foo = $END | B = bbb | '*' = $END || bbb"); // AST creation (and DSL printing from it) assertEquals("aaa | foo = $END | B = bbb | '*' = $END || bbb", js.stringify()); // Graph creation // assertEquals( // toExpectedGraph("n:0:START,n:1:aaa,n:2:bbb,n:3:ccc,n:4:END," + // "l:0:1,l:2:3,l:1:4:transitionName=foo,l:1:2:transitionName=B,l:1:3:transitionName='*',l:3:4"), // js.toGraph().toJSON()); // Graph to DSL checkDSLToGraphAndBackToDSL("aaa | foo = $END | B = bbb | '*' = $END"); // toXML assertEquals(loadXml("end3"), js.toXML("test1", true)); } @Test public void toGraph$FAIL() { js = parse("foo | oranges=$FAIL"); // AST creation (and DSL printing from it) assertEquals("foo | oranges = $FAIL", js.stringify()); // {"nodes":[{"id":"0","name":"START"},{"id":"1","name":"foo"},{"id":"2","name":"FAIL"}, // {"id":"3","name":"END"}], // "links":[{"from":"0","to":"1"},{"from":"1","to":"2","properties":{"transitionName":"oranges"}}, // {"from":"2","to":"3"}]} // Graph creation assertEquals(toExpectedGraph("n:0:START,n:1:foo,n:2:FAIL,n:3:END,l:0:1,l:1:2:transitionName=oranges,l:2:3"), js.toGraph().toJSON()); // Graph to DSL checkDSLToGraphAndBackToDSL("foo | oranges = $FAIL"); // XML creation assertEquals(loadXml("fail1"), js.toXML("test1", true)); } @Test public void toGraphInlineJob() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("filejdbc foo --aaa=bbb || bar"); assertEquals(toExpectedGraph( "n:0:START,n:1:foo:meta-jobModuleName=filejdbc;aaa=bbb,n:2:bar,n:3:END,l:0:1,l:1:2,l:2:3"), js.toGraph().toJSON()); } else { checkForParseError("filejdbc foo --aaa=bbb || bar", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 9, "foo"); } } @Test public void toGraphSequence2() { js = parse("foo || bar || boo"); assertEquals(toExpectedGraph("n:0:START,n:1:foo,n:2:bar,n:3:boo,n:4:END,l:0:1,l:1:2,l:2:3,l:3:4"), js.toGraph().toJSON()); } @Test public void toGraphSplit() { js = parse("<foo & bar> || boo"); assertEquals( toExpectedGraph("n:0:START,n:1:foo,n:2:bar,n:3:boo,n:4:END,l:0:1,l:0:2,l:1:3,l:2:3,l:3:4"), js.toGraph().toJSON()); } // TODO should & end the boo job name? Don't think it does right now // js = parse("<foo | completed=boo& bar> || boo"); @Test public void toGraphWithTransition() throws Exception { js = parse("<foo | completed=goo & bar> || boo || goo"); // {"nodes":[{"id":"0","name":"START"},{"id":"1","name":"foo"},{"id":"2","name":"goo"}, // {"id":"3","name":"bar"},{"id":"4","name":"boo"},{"id":"5","name":"goo"},{"id":"6","name":"END"}], // "links":[{"from":"0","to":"1"},{"from":"1","to":"2","properties":{"transitionName":"completed"}}, // {"from":"0","to":"3"},{"from":"2","to":"4"},{"from":"3","to":"4"},{"from":"4","to":"5"}, // {"from":"5","to":"6"}]} assertEquals( toExpectedGraph( "n:0:START,n:1:foo,n:2:goo,n:3:bar,n:4:boo,n:5:goo,n:6:END,l:0:1,l:1:2:transitionName=completed,l:0:3,l:2:4,l:3:4,l:4:5,l:5:6"), js.toGraph().toJSON()); } @Test public void toGraphWithTransition2() { // The target transition node is not elsewhere on the list js = parse("<foo | completed=hoo & bar> || boo || goo"); checkDSLToGraphAndBackToDSL("<foo | completed = hoo & bar> || boo || goo"); // {"nodes":[{"id":"0","name":"START"},{"id":"1","name":"foo"},{"id":"2","name":"hoo"}, // {"id":"3","name":"bar"},{"id":"4","name":"boo"},{"id":"5","name":"goo"},{"id":"6","name":"END"}], // "links":[{"from":"0","to":"1"},{"from":"1","to":"2","properties":{"transitionName":"completed"}}, // {"from":"0","to":"3"},{"from":"2","to":"4"},{"from":"3","to":"4"},{"from":"4","to":"5"}, // {"from":"5","to":"6"}]} assertEquals( toExpectedGraph( "n:0:START,n:1:foo,n:2:hoo,n:3:bar,n:4:boo,n:5:goo,n:6:END,l:0:1,l:1:2:transitionName=completed,l:0:3,l:2:4,l:3:4,l:4:5,l:5:6"), js.toGraph().toJSON()); } @Test public void toXML() { js = parse("foo"); assertEquals(loadXml("simpleJob"), js.toXML("test1", true)); } @Test public void toXMLallExitsCovered() { js = parse("aaa | foo = $END | B = bbb | '*' = ccc || bbb || ccc"); assertEquals(loadXml("allExitsCovered"), js.toXML("test1", true)); } @Ignore @Test public void toXMLSqoopExample() { js = parse("<(sqoop-6e44 | 'FAILED' = kill1\n" + " || sqoop-e07a | 'FAILED' = kill1) & \n" + " (sqoop-035f | 'FAILED' = kill2\n" + " || sqoop-9408 | 'FAILED' = kill2\n" + " || sqoop-a6e0 | 'FAILED' = kill2\n" + " || sqoop-e522 | 'FAILED' = kill2\n" + " || shell-b521 | 'FAILED' = kill2) & \n" + " (sqoop-6420 | 'FAILED' = kill3)>"); assertEquals(loadXml("sqoopJob"), js.toXML("test1", true)); } @Test public void toXMLFlow() { js = parse("foo || bar"); assertEquals(loadXml("simpleFlow"), js.toXML("test1", true)); } @Test public void toXMLFlowDup() { js = parse("foo || foo"); assertEquals(loadXml("xmlFlowDup"), js.toXML("test1", true)); } @Test public void toXMLSplitDup() { js = parse("<foo & foo>"); assertEquals(loadXml("xmlSplitDup"), js.toXML("test1", true)); } @Test public void toXMLFlowWithTransition() { js = parse("foo || bar | failed = goo"); assertEquals(loadXml("simpleFlowWithTransition"), js.toXML("test1", true)); } @Test public void toXMLSplitWithTransition() { js = parse("<foo | failed = boo & bar | failed = goo>"); assertEquals(loadXml("simpleSplitWithTransition"), js.toXML("test1", true)); } // Both failed transitions point to 'boo' - as it is across a split there should be separate boo // entries (with appropriate IDs) @Test public void toXMLSplitWithTransition2() { js = parse("<foo | failed = boo & bar | failed = boo>"); assertEquals(loadXml("simpleSplitWithTransition2"), js.toXML("test1", true)); } @Test public void toXMLWithForwardReference() { js = parse("aaa | failed = ccc || bbb || ccc || ddd"); assertEquals(loadXml("forwardReference"), js.toXML("test1", true)); } @Test public void toXMLSplitWithTransition3() { js = parse("<foo | failed = boo | error = boo & bar | failed = boo>"); assertEquals(loadXml("simpleSplitWithTransition3"), js.toXML("test1", true)); } @Test public void toXMLSplitWithTransition4() { js = parse("<foo | failed = boo | error = boo || goo & bar | failed = boo>"); assertEquals(loadXml("simpleSplitWithTransition4"), js.toXML("test1", true)); } @Test public void toXMLFlowWithMultiTransitionToSameTarget() { js = parse("foo | failed = bbb || bar | failed = bbb"); assertEquals(loadXml("simpleFlowWithTransition2"), js.toXML("test1", true)); } @Test public void toXMLFlowWithMultiTransitionOnSameJobToSameTarget() { js = parse("foo | failed = bbb | error = bbb || bar | failed = bbb"); assertEquals("foo | failed = bbb | error = bbb || bar | failed = bbb", js.stringify()); assertEquals(loadXml("simpleFlowWithTransition3"), js.toXML("test1", true)); } @Test public void toXMLFlowWithLotsTransitions() { js = parse("foo | failed = bbb | error = boo || bar | failed = bbb"); assertEquals(loadXml("simpleFlowWithTransition4"), js.toXML("test1", true)); } @Test public void toXMLFlowDupModule() { js = parse("foo || foo"); assertEquals(loadXml("simpleFlowDupModule"), js.toXML("test1", true)); } @Test public void toXMLFlowSplitDupModule() { js = parse("foo || <foo & foo>"); assertEquals(loadXml("flowSplitDupModule"), js.toXML("test1", true)); } @Test public void toXML1() { js = parse("aaa || <bbb & ccc> || farg || ddd"); assertXml("xml1", js.toXML("foo_COMPOSED", true)); } @Test public void lotsOfTransitions() { js = parse("aaa | 'FAILED'=iii ||< bbb | 'FAILED'=iii & ccc | 'FAILED'=jjj> ||" + "ddd| 'FAILED'=iii||eee| 'FAILED'=iii||fff| 'FAILED'=iii||<ggg| 'FAILED'=kkk & hhh| 'FAILED'=kkk>"); assertEquals( "aaa | 'FAILED' = iii || <bbb | 'FAILED' = iii & ccc | 'FAILED' = jjj> || " + "ddd | 'FAILED' = iii || eee | 'FAILED' = iii || fff | 'FAILED' = iii || <ggg | 'FAILED' = kkk & hhh | 'FAILED' = kkk>", js.stringify()); assertXml("lotsOfTransitions", js.toXML("lotsOfTransitions", true)); } @Test public void lotsOfTransitions2() { js = parse("aaa | 'FAILED'=iii ||< bbb | 'FAILED'=iii|| zzz & ccc | 'FAILED'=jjj> ||" + "ddd| 'FAILED'=iii||eee| 'FAILED'=iii||fff| 'FAILED'=iii||<ggg| 'FAILED'=kkk & hhh| 'FAILED'=kkk>"); assertEquals( "aaa | 'FAILED' = iii || <bbb | 'FAILED' = iii || zzz & ccc | 'FAILED' = jjj> || " + "ddd | 'FAILED' = iii || eee | 'FAILED' = iii || fff | 'FAILED' = iii || <ggg | 'FAILED' = kkk & hhh | 'FAILED' = kkk>", js.stringify()); assertXml("lotsOfTransitions2", js.toXML("lotsOfTransitions2", true)); } @Test public void toXML2() { js = parse("aaa|| bbb || farg | 'FAILED'=ccc ||ddd"); assertXml("xml2", js.toXML("foo_COMPOSED", true)); } @Test public void toXMLFlowDupModule2() { js = parse("foo || bar || foo || <goo & foo> || foo"); assertEquals(loadXml("simpleFlowDupModule2"), js.toXML("test1", true)); } @Test public void toXMLFlowRefOptions() { js = parse("foo || bar || boo --timeout=100"); assertEquals(loadXml("simpleFlowOptions"), js.toXML("test1", true)); } @Test public void toXMLFlowRefOptions2() { js = parse("foo || bar || boo --timeout=100 --pollInterval=999 "); assertEquals(loadXml("simpleFlowOptions2"), js.toXML("test1", true)); } @Test public void toXMLFlowRefOptions3() { js = parse("foo || bar || boo --zz='abc' --timeout=100 --pollInterval=999 --xx=99 --yy=custard "); assertEquals(loadXml("simpleFlowOptions3"), js.toXML("test1", true)); js = parse("foo || bar || boo --zz='abc' --timeout=100 --pollInterval=999 --xx=99 --yy=custard"); assertEquals(loadXml("simpleFlowOptions3"), js.toXML("test1", true)); } @Test public void toXMLSplit() { js = parse("<foo & bar>"); assertEquals(loadXml("simpleSplit"), js.toXML("test1", true)); } @Test public void toXmlFlowSplit() { js = parse("AA || <BB & CC> || DD"); assertEquals(loadXml("flowSplit"), js.toXML("test1", true)); } @Test public void toXmlLongFlowSplit() { js = parse("AA || <BB || CC & DD> || EE"); assertEquals(loadXml("flowSplit2"), js.toXML("test1", true)); } @Test public void inlineJobDefinitionWithOptions() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("jobModuleA jobNameA --foo=bar --boo=gar"); JobNode jn = js.getJobNode(); assertTrue(jn.isJobDescriptor()); JobDefinition jd = (JobDefinition) jn; assertFalse(jd.isReference()); assertTrue(jd.isDefinition()); assertEquals("jobModuleA", jd.getJobModuleName()); assertEquals("jobNameA", jd.getJobName()); ArgumentNode[] args = jd.getArguments(); assertEquals(2, args.length); assertEquals("--foo=bar", args[0].stringify()); assertEquals("--boo=gar", args[1].stringify()); } else { checkForParseError( "jobModuleA jobNameA --foo=bar --boo=gar", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 11, "jobNameA"); } } @Test public void extraneousDataError() { String jobSpecification = "<a & b> rubbish"; checkForParseError(jobSpecification, JobDSLMessage.UNEXPECTED_DATA_AFTER_JOBSPEC, 8, "rubbish"); } @Test public void incorrectTransition() { checkForParseError("foo | || = bar", JobDSLMessage.EXPECTED_TRANSITION_NAME, 6, "||"); } @Test public void spacesInTransitionNameRequireQuotes() { checkForParseError("foo | abc def = bar", JobDSLMessage.EXPECTED_EQUALS_AFTER_TRANSITION_NAME, 10, "def"); parse("foo | 'abc def' = bar"); } @Test public void inlineJobDefWithTwoArguments() { String dsl = "foo foo --name=value --x=y"; if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse(dsl); List<JobDefinition> jds = js.getJobDefinitions(); assertEquals(1, jds.size()); assertEquals(dsl, jds.get(0).stringify(false)); } else { checkForParseError(dsl, JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); } } @Test public void inlineJobDefDottedArgName() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("foo foo --nam.eee=value --x=y"); List<JobDefinition> jds = js.getJobDefinitions(); assertEquals(1, jds.size()); assertEquals("foo foo --nam.eee=value --x=y", jds.get(0).stringify(false)); } else { checkForParseError("foo foo --nam.eee=value --x=y", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); checkForParseError("foo foo --nam.eee=value --x=y", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); } } @Test public void inlineJobDefDottedArgValue() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("foo foo --name=abc.def --x=y"); List<JobDefinition> jds = js.getJobDefinitions(); assertEquals(1, jds.size()); assertEquals("foo foo --name=abc.def --x=y", jds.get(0).stringify(false)); } else { checkForParseError("foo foo --name=abc.def --x=y", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); } } @Test public void inlineJobDefWithArgumentsAndTransitions() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { js = parse("foo foo --name=value --x=y | aaa=bbb | ccc=ddd | eee=fff"); List<JobDefinition> jds = js.getJobDefinitions(); assertEquals(1, jds.size()); assertEquals("foo foo --name=value --x=y | aaa = bbb | ccc = ddd | eee = fff", jds.get(0).stringify(false)); js = parse("foo foo --name=value --x=y | aaa=bbb | ccc=ddd | eee=fff || bar bart || goo good"); jds = js.getJobDefinitions(); assertEquals(3, jds.size()); assertEquals("bar bart", jds.get(1).stringify(false)); } else { checkForParseError("foo foo --name=value --x=y | aaa=bbb | ccc=ddd | eee=fff", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); checkForParseError("foo foo --name=value --x=y | aaa = bbb | ccc = ddd | eee = fff", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); checkForParseError("foo foo --name=value --x=y | aaa=bbb | ccc=ddd | eee=fff || bar bart || goo good", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "foo"); } } @Test public void inlineJobDefinitionNeedsName() { checkForParseError("foo || transform --expression=new StringBuilder(payload).reverse() || bar", JobDSLMessage.UNEXPECTED_DATA_AFTER_JOBSPEC, 34); } @Test public void argumentsNeedQuotesAroundArgValueWithSpaces() { String dsl = "foo || transform bar --expression=new StringBuilder(payload).reverse() || bar"; if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { checkForParseError(dsl, JobDSLMessage.UNEXPECTED_DATA_AFTER_JOBSPEC, 38); // TODO [asc] How/when are the quotes removed for these argument values? js = parse("foo || transform bar --expression='new StringBuilder(payload).reverse()' || bar"); } else { checkForParseError(dsl, JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 17, "bar"); } } @Test public void moduleArguments_xd1613() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { // notice no space between the ' and final > js = parse( "transform X --expression='payload.toUpperCase()' || filter Y --expression='payload.length() > 4'"); assertEquals("payload.toUpperCase()", js.getJobDefinition("X").getArguments()[0].getValue()); assertEquals("payload.length() > 4", js.getJobDefinition("Y").getArguments()[0].getValue()); js = parse( "time || transform X --expression='T(org.joda.time.format.DateTimeFormat).forPattern(\"yyyy-MM-dd HH:mm:ss\").parseDateTime(payload)'"); assertEquals( "T(org.joda.time.format.DateTimeFormat).forPattern(\"yyyy-MM-dd HH:mm:ss\").parseDateTime(payload)", js.getJobDefinition("X").getArguments()[0].getValue()); // allow for pipe/semicolon if quoted js = parse("http || transform X --outputType='text/plain|charset=UTF-8' || log"); assertEquals("text/plain|charset=UTF-8", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --outputType='text/plain;charset=UTF-8' || log"); assertEquals("text/plain;charset=UTF-8", js.getJobDefinition("X").getArguments()[0].getValue()); // Want to treat all of 'hi'+payload as the argument value js = parse("http || transform X --expression='hi'+payload || log"); assertEquals("'hi'+payload", js.getJobDefinition("X").getArguments()[0].getValue()); // Want to treat all of payload+'hi' as the argument value js = parse("http || transform X --expression=payload+'hi' || log"); assertEquals("payload+'hi'", js.getJobDefinition("X").getArguments()[0].getValue()); // Alternatively, can quote all around it to achieve the same thing js = parse("http || transform X --expression='payload+''hi''' || log"); assertEquals("payload+'hi'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression='''hi''+payload' || log"); assertEquals("'hi'+payload", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression=\"payload+'hi'\" || log"); assertEquals("payload+'hi'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression=\"'hi'+payload\" || log"); assertEquals("'hi'+payload", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression=payload+'hi'--param2='foobar' || log"); assertEquals("payload+'hi'--param2='foobar'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression='hi'+payload--param2='foobar' || log"); assertEquals("'hi'+payload--param2='foobar'", js.getJobDefinition("X").getArguments()[0].getValue()); // This also works, which is cool js = parse("http || transform X --expression='hi'+'world' || log"); assertEquals("'hi'+'world'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression=\"'hi'+'world'\" || log"); assertEquals("'hi'+'world'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || filter X --expression=payload.matches('hello world') || log"); assertEquals("payload.matches('hello world')", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression='''hi''' || log"); assertEquals("'hi'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform X --expression=\"''''hi''''\" || log"); assertEquals("''''hi''''", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http asdf --port=9014 || filter X --expression=\"payload == 'foo'\" || log"); assertEquals("payload == 'foo'", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http asdf --port=9014 || filter X --expression='new Foo()' || log"); assertEquals("new Foo()", js.getJobDefinition("X").getArguments()[0].getValue()); js = parse("http || transform XX --expression='payload.replace(\"abc\", \"\")' || log"); assertEquals("payload.replace(\"abc\", \"\")", js.getJobDefinition("XX").getArguments()[0].getValue()); js = parse("http || transform XX --expression='payload.replace(\"abc\", '''')' || log"); assertEquals("payload.replace(\"abc\", '')", js.getJobDefinition("XX").getArguments()[0].getValue()); } else { checkForParseError( "transform X --expression='payload.toUpperCase()' || filter Y --expression='payload.length() > 4'", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 10, "X"); } } // Parameters must be constructed via adjacent tokens @Test public void needAdjacentTokensForParameters() { if (JobParser.SUPPORTS_INLINE_JOB_DEFINITIONS) { checkForParseError("foo a -- name=value", JobDSLMessage.NO_WHITESPACE_BEFORE_ARG_NAME, 9); checkForParseError("foo a --name =value", JobDSLMessage.NO_WHITESPACE_BEFORE_ARG_EQUALS, 13); checkForParseError("foo a --name= value", JobDSLMessage.NO_WHITESPACE_BEFORE_ARG_VALUE, 14); } else { checkForParseError("foo a -- name=value", JobDSLMessage.EXPECTED_FLOW_OR_SPLIT_CHARS, 4, "a"); } } // -- /** * Create a parser for test use. */ private JobParser getParser() { return new JobParser(); } private JobSpecification parse(String jobSpecification) { JobSpecification jobSpec = getParser().parse(jobSpecification); return jobSpec; } /** * Convert the supplied internal DSL (for test usage only) into a graph against which we can * compare the actual output from the graph creation code. * @param graphTestDSL the shorthand DSL for a graph description * @return a JSON string representing the complete graph */ private String toExpectedGraph(String graphTestDSL) { StringBuilder s = new StringBuilder(); s.append("{"); s.append("\"nodes\":["); StringTokenizer st = new StringTokenizer(graphTestDSL, ","); boolean onLinksNow = false; int counter = 0; while (st.hasMoreElements()) { String element = st.nextToken(); StringTokenizer elementTokenizer = new StringTokenizer(element, ":"); String type = elementTokenizer.nextToken(); if (type.equals("n")) { String nodeId = elementTokenizer.nextToken(); String nodeName = elementTokenizer.nextToken(); if (counter > 0) { s.append(","); } s.append("{\"id\":\"" + nodeId + "\",\"name\":\"" + nodeName + "\""); if (elementTokenizer.hasMoreTokens()) { String propertiesAndMetadata = elementTokenizer.nextToken(); StringBuilder metadata = new StringBuilder(); StringBuilder properties = new StringBuilder(); StringTokenizer propertyOrMetadataTokenizer = new StringTokenizer(propertiesAndMetadata, ";"); metadata.append(",\"metadata\":{"); properties.append(",\"properties\":{"); int propertyCount = 0; int metadataCount = 0; while (propertyOrMetadataTokenizer.hasMoreTokens()) { String propertyOrMetadata = propertyOrMetadataTokenizer.nextToken(); int equals = propertyOrMetadata.indexOf("="); String key = propertyOrMetadata.substring(0, equals); String value = propertyOrMetadata.substring(equals + 1); if (key.startsWith("meta-")) { key = key.substring(5); if (metadataCount > 0) { metadata.append(","); } metadata.append("\"" + key + "\":\"" + value + "\""); metadataCount++; } else { if (propertyCount > 0) { s.append(","); } properties.append("\"" + key + "\":\"" + value + "\""); propertyCount++; } } if (metadataCount > 0) { s.append(metadata.toString()).append("}"); } if (propertyCount > 0) { s.append(properties.toString()); } s.append("}"); } s.append("}"); } else { if (!onLinksNow) { s.append("],\"links\":["); onLinksNow = true; counter = 0; } if (counter > 0) { s.append(","); } String sourceId = elementTokenizer.nextToken(); String targetId = elementTokenizer.nextToken(); s.append("{\"from\":\"" + sourceId + "\",\"to\":\"" + targetId + "\""); if (elementTokenizer.hasMoreTokens()) { String properties = elementTokenizer.nextToken(); StringTokenizer propertyTokenizer = new StringTokenizer(properties, ";"); s.append(",\"properties\":{"); int propertyCount = 0; while (propertyTokenizer.hasMoreTokens()) { if (propertyCount > 0) { s.append(","); } String property = propertyTokenizer.nextToken(); int equals = property.indexOf("="); String key = property.substring(0, equals); String value = property.substring(equals + 1); s.append("\"" + key + "\":\"" + value + "\""); } s.append("}"); } s.append("}"); } counter++; } s.append("]}"); return s.toString(); } /** * Load a resource XML file that represents the expected test output. */ private String loadXml(String xmlfile) { try { String resourceName = this.getClass().getPackage().getName().toString().replace('.', File.separatorChar) + File.separator + xmlfile + ".xml"; InputStream istream = getClass().getClassLoader().getResourceAsStream(resourceName); BufferedInputStream bis = new BufferedInputStream(istream); byte[] theData = new byte[10000000]; int dataReadSoFar = 0; byte[] buffer = new byte[1024]; int read = 0; while ((read = bis.read(buffer)) != -1) { System.arraycopy(buffer, 0, theData, dataReadSoFar, read); dataReadSoFar += read; } bis.close(); byte[] returnData = new byte[dataReadSoFar]; System.arraycopy(theData, 0, returnData, 0, dataReadSoFar); return new String(returnData); } catch (IOException ioe) { throw new IllegalStateException(ioe); } } private void checkForParseError(String jobSpecification, JobDSLMessage msg, int pos, Object... inserts) { try { JobSpecification js = parse(jobSpecification); fail("expected to fail but parsed " + js.stringify()); } catch (JobSpecificationException e) { e.printStackTrace(); assertEquals(msg, e.getMessageCode()); assertEquals(pos, e.getPosition()); if (inserts != null) { for (int i = 0; i < inserts.length; i++) { assertEquals(inserts[i], e.getInserts()[i]); } } } } private void checkDSLToGraphAndBackToDSL(String specification) { js = parse(specification); Graph g = js.toGraph(); assertEquals(specification, g.toDSLText()); } private void assertToken(TokenKind kind, String string, int start, int end, Token t) { assertEquals(kind, t.kind); assertEquals(string, t.getKind().hasPayload() ? t.stringValue() : new String(t.getKind().getTokenChars())); assertEquals(start, t.startpos); assertEquals(end, t.endpos); } private void assertXml(String xmlFileName, String expectedText) { assertEquals(loadXml(xmlFileName), expectedText); } }