Package org.springframework.xd.dirt.stream.dsl

Source Code of org.springframework.xd.dirt.stream.dsl.StreamConfigParserTests

/*
* Copyright 2013-2014 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.stream.dsl;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.springframework.xd.dirt.stream.ParsingContext.stream;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.springframework.data.repository.CrudRepository;
import org.springframework.xd.dirt.module.ModuleRegistry;
import org.springframework.xd.dirt.stream.StreamDefinition;
import org.springframework.xd.dirt.stream.XDStreamParser;
import org.springframework.xd.dirt.zookeeper.EmbeddedZooKeeper;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnection;
import org.springframework.xd.module.options.DefaultModuleOptionsMetadataResolver;

/**
* Parse streams and verify either the correct abstract syntax tree is produced or the current exception comes out.
*
* @author Andy Clement
* @author David Turanski
*/
public class StreamConfigParserTests {

  @Rule
  public ExpectedException thrown = ExpectedException.none();

  private StreamNode sn;

  private EmbeddedZooKeeper zk = new EmbeddedZooKeeper();

  private ZooKeeperConnection zooKeeperConnection;

  private TestRepository testRepository = new TestRepository();

  @Before
  public void setUp() {
    zk = new EmbeddedZooKeeper();
    zk.start();
    zooKeeperConnection = new ZooKeeperConnection("localhost:" + zk.getClientPort());
  }

  // This is not a well formed stream but we are testing single module parsing
  @Test
  public void oneModule() {
    sn = parse("foo");
    assertEquals(1, sn.getModuleNodes().size());
    ModuleNode mn = sn.getModule("foo");
    assertEquals("foo", mn.getName());
    assertEquals(0, mn.getArguments().length);
    assertEquals(0, mn.startpos);
    assertEquals(3, mn.endpos);
  }

  @Test
  public void hyphenatedModuleName() {
    sn = parse("gemfire-cq");
    assertEquals("[(ModuleNode:gemfire-cq:0>10)]", sn.stringify(true));
  }

  // Just to make the testing easier the parser supports stream naming easier.
  @Test
  public void streamNaming() {
    sn = parse("mystream = foo");
    assertEquals("[mystream = (ModuleNode:foo:11>14)]", sn.stringify(true));
    assertEquals("mystream", sn.getName());
  }

  // Test if the DSLException thrown when the stream name is same as that of any of its modules' names.
  @Test
  public void testInvalidStreamName() {
    String streamName = "bar";
    String stream = "foo | bar";
    checkForParseError(streamName, stream, XDDSLMessages.STREAM_NAME_MATCHING_MODULE_NAME,
        stream.indexOf(streamName), streamName);
  }

  // Pipes are used to connect modules
  @Test
  public void twoModules() {
    StreamNode ast = parse("foo | bar");
    assertEquals("[(ModuleNode:foo:0>3)(ModuleNode:bar:6>9)]", ast.stringify(true));
  }

  // Modules can be labeled
  @Test
  public void moduleLabels() {
    StreamNode ast = parse("label: http");
    assertEquals("[((Label:label:0>5) ModuleNode:http:0>11)]", ast.stringify(true));
  }

  @Test
  public void moduleLabels3() {
    StreamNode ast = parse("food = http | label3: foo");
    assertEquals(
        "[food = (ModuleNode:http:7>11)((Label:label3:14>20) ModuleNode:foo:14>25)]",
        ast.stringify(true));

    sn = parse("http | foo:bar | file");
    assertEquals("[(ModuleNode:http)((Label:foo) ModuleNode:bar)(ModuleNode:file)]", sn.stringify());

    checkForParseError("http | foo: goggle: bar | file", XDDSLMessages.UNEXPECTED_DATA_AFTER_STREAMDEF,
        18);
    checkForParseError("http | foo :bar | file", XDDSLMessages.NO_WHITESPACE_BETWEEN_LABEL_NAME_AND_COLON, 11);
  }

  // Modules can take parameters
  @Test
  public void oneModuleWithParam() {
    StreamNode ast = parse("foo --name=value");
    assertEquals("[(ModuleNode:foo --name=value:0>16)]", ast.stringify(true));
  }

  // Modules can take two parameters
  @Test
  public void oneModuleWithTwoParams() {
    StreamNode sn = parse("foo --name=value --x=y");
    List<ModuleNode> moduleNodes = sn.getModuleNodes();
    assertEquals(1, moduleNodes.size());

    ModuleNode mn = moduleNodes.get(0);
    assertEquals("foo", mn.getName());
    ArgumentNode[] args = mn.getArguments();
    assertNotNull(args);
    assertEquals(2, args.length);
    assertEquals("name", args[0].getName());
    assertEquals("value", args[0].getValue());
    assertEquals("x", args[1].getName());
    assertEquals("y", args[1].getValue());

    assertEquals("[(ModuleNode:foo --name=value --x=y:0>22)]", sn.stringify(true));
  }

  @Test
  public void testParameters() {
    String module = "gemfire-cq --query='Select * from /Stocks where symbol=''VMW''' --regionName=foo --foo=bar";
    StreamNode ast = parse(module);
    ModuleNode gemfireModule = ast.getModule("gemfire-cq");
    Properties parameters = gemfireModule.getArgumentsAsProperties();
    assertEquals(3, parameters.size());
    assertEquals("Select * from /Stocks where symbol='VMW'", parameters.get("query"));
    assertEquals("foo", parameters.get("regionName"));
    assertEquals("bar", parameters.get("foo"));

    module = "test";
    parameters = parse(module).getModule("test").getArgumentsAsProperties();
    assertEquals(0, parameters.size());

    module = "foo --x=1 --y=two ";
    parameters = parse(module).getModule("foo").getArgumentsAsProperties();
    assertEquals(2, parameters.size());
    assertEquals("1", parameters.get("x"));
    assertEquals("two", parameters.get("y"));

    module = "foo --x=1a2b --y=two ";
    parameters = parse(module).getModule("foo").getArgumentsAsProperties();
    assertEquals(2, parameters.size());
    assertEquals("1a2b", parameters.get("x"));
    assertEquals("two", parameters.get("y"));

    module = "foo --x=2";
    parameters = parse(module).getModule("foo").getArgumentsAsProperties();
    assertEquals(1, parameters.size());
    assertEquals("2", parameters.get("x"));

    module = "--foo = bar";
    try {
      parse(module);
      fail(module + " is invalid. Should throw exception");
    }
    catch (Exception e) {
      // success
    }
  }

  @Test
  public void testInvalidModules() {
    String config = "test | foo--x=13";
    XDStreamParser parser = new XDStreamParser(testRepository, mock(ModuleRegistry.class),
        new DefaultModuleOptionsMetadataResolver());
    try {
      parser.parse("t", config, stream);
      fail(config + " is invalid. Should throw exception");
    }
    catch (Exception e) {
      // success
    }
  }

  @Test
  public void tapWithLabelReference() {
    parse("mystream = http | filter | group1: transform | group2: transform | file");
    StreamNode ast = parse("tap:stream:mystream.group1 > file");
    // post resolution 'group1' is transformed to transform
    assertEquals("[(tap:stream:mystream.transform.2)>(ModuleNode:file)]", ast.stringify());
    // TODO: Index should still be present in this case
    ast = parse("tap:stream:mystream > file");
    assertEquals("[(tap:stream:mystream.http.0)>(ModuleNode:file)]", ast.stringify());
  }

  @Test
  public void tapWithQualifiedModuleReference() {
    parse("mystream = http | foobar | file");
    StreamNode sn = parse("tap:stream:mystream.foobar > file");
    assertEquals("[(tap:stream:mystream.foobar.1:0>26)>(ModuleNode:file:29>33)]", sn.stringify(true));
  }

  @Test
  public void tapOnNonExistentStreamFails() throws Exception {
    thrown.expect(StreamDefinitionException.class);
    thrown.expect(hasProperty("position", is(equalTo("tap:stream:".length()))));

    parse("tap:stream:mystream.foobar > file");
  }

  @Test
  public void tapOnNonExistentStreamFails2() throws Exception {
    thrown.expect(StreamDefinitionException.class);
    thrown.expect(hasProperty("position", is(equalTo("tap:stream:".length()))));

    parse("tap:stream:mystream.foobar.1 > file");
  }

  @Test
  public void tapOnNonExistentStreamModuleFails() throws Exception {
    parse("mystream = http | file");

    thrown.expect(StreamDefinitionException.class);
    thrown.expect(hasProperty("position", is(equalTo("tap:stream:mystream.".length()))));
    parse("tap:stream:mystream.foobar > log");
  }

  @Test
  public void expressions_xd159() {
    StreamNode ast = parse("foo | transform --expression=--payload | bar");
    ModuleNode mn = ast.getModule("transform");
    Properties props = mn.getArgumentsAsProperties();
    assertEquals("--payload", props.get("expression"));
  }

  @Test
  public void expressions_xd159_2() {
    // need quotes around an argument value with a space in it
    checkForParseError("foo | transform --expression=new StringBuilder(payload).reverse() | bar",
        XDDSLMessages.UNEXPECTED_DATA, 46);
  }

  @Test
  public void ensureStreamNamesValid_xd1344() {
    // Similar rules to a java identifier but also allowed '-' after the first char
    checkForIllegalStreamName("foo.bar", "http | transform | sink");
    checkForIllegalStreamName("-bar", "http | transform | sink");
    checkForIllegalStreamName(".bar", "http | transform | sink");
    checkForIllegalStreamName("foo-.-bar", "http | transform | sink");
    checkForIllegalStreamName("0foobar", "http | transform | sink");
    checkForIllegalStreamName("foo%bar", "http | transform | sink");
    parse("foo-bar", "http | transform | sink");
    parse("foo_bar", "http | transform | sink");
  }

  @Test
  public void expressions_xd159_3() {
    StreamNode ast = parse("foo |  transform --expression='new StringBuilder(payload).reverse()' | bar");
    ModuleNode mn = ast.getModule("transform");
    Properties props = mn.getArgumentsAsProperties();
    assertEquals("new StringBuilder(payload).reverse()", props.get("expression"));
  }

  @Test
  public void moduleArguments_xd1613() {
    StreamNode ast = null;

    // notice no space between the ' and final >
    ast = parse("queue:producer > transform --expression='payload.toUpperCase()' | filter --expression='payload.length() > 4'> queue:consumer");
    assertEquals("payload.toUpperCase()", ast.getModule("transform").getArguments()[0].getValue());
    assertEquals("payload.length() > 4", ast.getModule("filter").getArguments()[0].getValue());

    ast = parse("time | transform --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)",
        ast.getModule("transform").getArguments()[0].getValue());

    // allow for pipe/semicolon if quoted
    ast = parse("http | transform --outputType='text/plain|charset=UTF-8'  | log");
    assertEquals("text/plain|charset=UTF-8", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | transform --outputType='text/plain;charset=UTF-8'  | log");
    assertEquals("text/plain;charset=UTF-8", ast.getModule("transform").getArguments()[0].getValue());

    // Want to treat all of 'hi'+payload as the argument value
    ast = parse("http | transform --expression='hi'+payload | log");
    assertEquals("'hi'+payload", ast.getModule("transform").getArguments()[0].getValue());

    // Want to treat all of payload+'hi' as the argument value
    ast = parse("http | transform --expression=payload+'hi' | log");
    assertEquals("payload+'hi'", ast.getModule("transform").getArguments()[0].getValue());

    // Alternatively, can quote all around it to achieve the same thing
    ast = parse("http | transform --expression='payload+''hi''' | log");
    assertEquals("payload+'hi'", ast.getModule("transform").getArguments()[0].getValue());
    ast = parse("http | transform --expression='''hi''+payload' | log");
    assertEquals("'hi'+payload", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | transform --expression=\"payload+'hi'\" | log");
    assertEquals("payload+'hi'", ast.getModule("transform").getArguments()[0].getValue());
    ast = parse("http | transform --expression=\"'hi'+payload\" | log");
    assertEquals("'hi'+payload", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | transform --expression=payload+'hi'--param2='foobar' | log");
    assertEquals("payload+'hi'--param2='foobar'", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | transform --expression='hi'+payload--param2='foobar' | log");
    assertEquals("'hi'+payload--param2='foobar'", ast.getModule("transform").getArguments()[0].getValue());

    // This also works, which is cool
    ast = parse("http | transform --expression='hi'+'world' | log");
    assertEquals("'hi'+'world'", ast.getModule("transform").getArguments()[0].getValue());
    ast = parse("http | transform --expression=\"'hi'+'world'\" | log");
    assertEquals("'hi'+'world'", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | filter --expression=payload.matches('hello world') | log");
    assertEquals("payload.matches('hello world')", ast.getModule("filter").getArguments()[0].getValue());

    ast = parse("http | transform --expression='''hi''' | log");
    assertEquals("'hi'", ast.getModule("transform").getArguments()[0].getValue());

    ast = parse("http | transform --expression=\"''''hi''''\" | log");
    assertEquals("''hi''", ast.getModule("transform").getArguments()[0].getValue());
  }

  @Test
  public void expressions_xd159_4() {
    StreamNode ast = parse("foo |  transform --expression=\"'Hello, world!'\" | bar");
    ModuleNode mn = ast.getModule("transform");
    Properties props = mn.getArgumentsAsProperties();
    assertEquals("'Hello, world!'", props.get("expression"));
    ast = parse("foo |  transform --expression='''Hello, world!''' | bar");
    mn = ast.getModule("transform");
    props = mn.getArgumentsAsProperties();
    assertEquals("'Hello, world!'", props.get("expression"));
    // Prior to the change for XD-1613, this error should point to the comma:
    // checkForParseError("foo |  transform --expression=''Hello, world!'' | bar", XDDSLMessages.UNEXPECTED_DATA,
    // 37);
    // but now it points to the !
    checkForParseError("foo |  transform --expression=''Hello, world!'' | bar", XDDSLMessages.UNEXPECTED_DATA, 44);
  }

  @Test
  public void expressions_gh1() {
    StreamNode ast = parse("http --port=9014 | filter --expression=\"payload == 'foo'\" | log");
    ModuleNode mn = ast.getModule("filter");
    Properties props = mn.getArgumentsAsProperties();
    assertEquals("payload == 'foo'", props.get("expression"));
  }

  @Test
  public void expressions_gh1_2() {
    StreamNode ast = parse("http --port=9014 | filter --expression='new Foo()' | log");
    ModuleNode mn = ast.getModule("filter");
    Properties props = mn.getArgumentsAsProperties();
    assertEquals("new Foo()", props.get("expression"));
  }

  @Test
  public void sourceChannel() {
    StreamNode sn = parse("queue:foobar > file");
    assertEquals("[(queue:foobar:0>12)>(ModuleNode:file:15>19)]", sn.stringify(true));
  }

  @Test
  public void sinkChannel() {
    StreamNode sn = parse("http > queue:foo");
    assertEquals("[(ModuleNode:http:0>4)>(queue:foo:7>16)]", sn.stringify(true));
  }

  @Test
  public void channelVariants() {
    // Job is not a legal channel prefix
    checkForParseError("trigger > job:foo", XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC, 10, "job");

    // This looks like a label and so file is treated as a sink!
    checkForParseError("queue: bar > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 7);

    // 'queue' looks like a module all by itself so everything after is unexpected
    checkForParseError("queue : bar > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 6);

    // 'queue' looks like a module all by itself so everything after is unexpected
    checkForParseError("queue :bar > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 6);

    checkForParseError("tap:queue: boo > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 11);
    checkForParseError("tap:queue :boo > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 10);
    checkForParseError("tap:queue : boo > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 10);

    checkForParseError("tap:stream:boo .xx > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 15);
    checkForParseError("tap:stream:boo . xx > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 15);
    checkForParseError("tap:stream:boo. xx > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 16);
    checkForParseError("tap:stream:boo.xx. yy > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 19);
    checkForParseError("tap:stream:boo.xx .yy > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 18);
    checkForParseError("tap:stream:boo.xx . yy > file", XDDSLMessages.NO_WHITESPACE_IN_CHANNEL_DEFINITION, 18);

    checkForParseError("tap:queue:boo.xx.yy > file", XDDSLMessages.ONLY_A_TAP_ON_A_STREAM_OR_JOB_CAN_BE_INDEXED, 13);

    sn = parse("wibble: http > queue:bar");
    assertEquals("[((Label:wibble) ModuleNode:http)>(queue:bar)]", sn.stringify());
  }

  @Test
  public void qualifiedSinkChannelError() {
    // Only the source channel supports a dotted suffix
    checkForParseError("http > queue:wibble.foo", XDDSLMessages.CHANNEL_INDEXING_NOT_ALLOWED, 19);
  }

  @Test
  public void sourceChannel2() {
    parse("foo = http | bar | file");
    StreamNode ast = parse("tap:stream:foo.bar > file");
    assertEquals("[(tap:stream:foo.bar.1:0>18)>(ModuleNode:file:21>25)]", ast.stringify(true));
    assertEquals("tap:stream:foo.bar.1", ast.getSourceChannelNode().getChannelName());
  }

  @Test
  public void sourceTapChannel() {
    StreamNode ast = parse("tap:queue:xxy > file");
    assertEquals("[(tap:queue:xxy:0>13)>(ModuleNode:file:16>20)]", ast.stringify(true));
  }

  @Test
  public void sourceTapChannel2() {
    parse("mystream = http | file");
    StreamNode ast = parse("tap:stream:mystream.http > file");
    assertEquals(
        "[(tap:stream:mystream.http.0:0>24)>(ModuleNode:file:27>31)]",
        ast.stringify(true));
  }

  @Test
  public void sourceTapChannelNoColon() {
    parse("mystream = http | file");
    StreamNode ast = null;
    SourceChannelNode sourceChannelNode = null;

    ast = parse("tap:stream:mystream.http > file");
    sourceChannelNode = ast.getSourceChannelNode();
    assertEquals("tap:stream:mystream.http.0", sourceChannelNode.getChannelName());
  }

  @Test
  public void sourceTapChannel3() {
    parse("mystream = http | file");
    StreamNode ast = null;
    SourceChannelNode sourceChannelNode = null;

    ast = parse("tap:stream:mystream.http > file");
    sourceChannelNode = ast.getSourceChannelNode();
    assertEquals("tap:stream:mystream.http.0", sourceChannelNode.getChannelName());
    assertEquals(ChannelType.TAP_STREAM, sourceChannelNode.getChannelType());

    ast = parse("tap:stream:mystream > file");
    sourceChannelNode = ast.getSourceChannelNode();
    // After resolution the name has been properly setup
    assertEquals("tap:stream:mystream.http.0", sourceChannelNode.getChannelName());
    assertEquals(ChannelType.TAP_STREAM, sourceChannelNode.getChannelType());
  }

  @Test
  public void nameSpaceTestWithSpaces() {
    checkForParseError("trigger > queue:job:myjob   too", XDDSLMessages.UNEXPECTED_DATA_AFTER_STREAMDEF, 28, "too");
  }

  @Test
  public void errorCases01() {
    checkForParseError(".", XDDSLMessages.EXPECTED_MODULENAME, 0, ".");
    checkForParseError(";", XDDSLMessages.EXPECTED_MODULENAME, 0, ";");
  }

  @Test
  public void errorCases04() {
    checkForParseError("foo bar=yyy", XDDSLMessages.UNEXPECTED_DATA_AFTER_STREAMDEF, 4, "bar");
    checkForParseError("foo bar", XDDSLMessages.UNEXPECTED_DATA_AFTER_STREAMDEF, 4, "bar");
  }

  @Test
  public void errorCases05() {
    checkForParseError("foo --", XDDSLMessages.OOD, 6);
    checkForParseError("foo --bar", XDDSLMessages.OOD, 9);
    checkForParseError("foo --bar=", XDDSLMessages.OOD, 10);
  }

  @Test
  public void errorCases06() {
    checkForParseError("|", XDDSLMessages.EXPECTED_MODULENAME, 0);
  }

  @Test
  public void errorCases07() {
    checkForParseError("foo > bar", XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC, 6, "bar");
    checkForParseError("foo >", XDDSLMessages.OOD, 5);
    checkForParseError("foo > --2323", XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC, 6, "--");
    checkForParseError("foo > *", XDDSLMessages.UNEXPECTED_DATA, 6, "*");
  }

  @Test
  public void errorCases08() {
    checkForParseError(":foo | bar", XDDSLMessages.EXPECTED_MODULENAME, 0, ":");
  }

  @Test
  public void errorCases09() {
    checkForParseError("* = http | file", XDDSLMessages.UNEXPECTED_DATA, 0, "*");
    checkForParseError(": = http | file", XDDSLMessages.ILLEGAL_STREAM_NAME, 0, ":");
  }

  @Test
  public void errorCase10() {
    checkForParseError("trigger > :job:foo", XDDSLMessages.EXPECTED_CHANNEL_PREFIX_QUEUE_TOPIC, 10, ":");
  }

  @Test
  public void errorCase11() {
    checkForParseError("tap:banana:yyy > file", XDDSLMessages.NOT_ALLOWED_TO_TAP_THAT, 4, "banana");
    checkForParseError("tap:xxx > file", XDDSLMessages.TAP_NEEDS_THREE_COMPONENTS, 0);
  }

  @Test
  public void duplicateExplicitLabels() {
    checkForParseError("xxx: http | xxx: file", XDDSLMessages.DUPLICATE_LABEL, 12, "xxx", "http", 0, "file", 1);
    checkForParseError("xxx: http | yyy: filter | transform | xxx: transform | file",
        XDDSLMessages.DUPLICATE_LABEL, 38, "xxx", "http", 0, "transform", 3);
    checkForParseError("xxx: http | yyy: filter | transform | xxx: transform | xxx: file",
        XDDSLMessages.DUPLICATE_LABEL, 38, "xxx", "http", 0, "transform", 3);
  }

  @Test
  public void addingALabelLiftsAmbiguity() {
    StreamNode ast = parse("file | out: file");
    assertEquals("file", ast.getModuleNodes().get(0).getLabelName());
    assertEquals("out", ast.getModuleNodes().get(1).getLabelName());

  }

  @Test
  public void duplicateImplicitLabels() {
    checkForParseError("http | filter | transform | transform | file",
        XDDSLMessages.DUPLICATE_LABEL, 28, "transform", "transform", 2, "transform", 3);
  }

  @Test
  public void tapWithLabels() {
    parse("mystream = http | flibble: transform | file");
    sn = parse("tap:stream:mystream.flibble > file");
    assertEquals("tap:stream:mystream.transform.1", sn.getSourceChannelNode().getChannelName());
  }

  @Test
  public void bridge01() {
    StreamNode sn = parse("queue:bar > topic:boo");
    assertEquals("[(queue:bar:0>9)>(ModuleNode:bridge:10>11)>(topic:boo:12>21)]", sn.stringify(true));
  }

  // Parameters must be constructed via adjacent tokens
  @Test
  public void needAdjacentTokensForParameters() {
    checkForParseError("foo -- name=value", XDDSLMessages.NO_WHITESPACE_BEFORE_ARG_NAME, 7);
    checkForParseError("foo --name =value", XDDSLMessages.NO_WHITESPACE_BEFORE_ARG_EQUALS, 11);
    checkForParseError("foo --name= value", XDDSLMessages.NO_WHITESPACE_BEFORE_ARG_VALUE, 12);
  }

  // ---

  @Test
  public void testComposedOptionNameErros() {
    checkForParseError("foo --name.=value", XDDSLMessages.NOT_EXPECTED_TOKEN, 11);
    checkForParseError("foo --name .sub=value", XDDSLMessages.NO_WHITESPACE_IN_DOTTED_NAME, 11);
    checkForParseError("foo --name. sub=value", XDDSLMessages.NO_WHITESPACE_IN_DOTTED_NAME, 12);
  }

  @After
  public void reset() {
    testRepository.reset();
  }

  private StreamConfigParser getParser() {
    return new StreamConfigParser(testRepository);
  }

  StreamNode parse(String streamDefinition) {
    StreamNode streamNode = getParser().parse(streamDefinition);
    if (streamNode.getStreamName() != null) {
      testRepository.save(new StreamDefinition(streamNode.getStreamName(), streamNode.getStreamData()));
    }
    return streamNode;
  }

  StreamNode parse(String streamName, String streamDefinition) {
    StreamNode streamNode = getParser().parse(streamName, streamDefinition);
    String sname = streamNode.getStreamName();
    if (sname == null) {
      sname = streamName;
    }
    if (sname != null) {
      testRepository.save(new StreamDefinition(sname, streamNode.getStreamData()));
    }
    return streamNode;
  }

  private void checkForIllegalStreamName(String streamName, String streamDef) {
    try {
      StreamNode sn = parse(streamName, streamDef);
      fail("expected to fail but parsed " + sn.stringify());
    }
    catch (StreamDefinitionException e) {
      assertEquals(XDDSLMessages.ILLEGAL_STREAM_NAME, e.getMessageCode());
      assertEquals(0, e.getPosition());
      assertEquals(streamName, e.getInserts()[0]);
    }
  }

  private void checkForParseError(String stream, XDDSLMessages msg, int pos, Object... inserts) {
    try {
      StreamNode sn = parse(stream);
      fail("expected to fail but parsed " + sn.stringify());
    }
    catch (StreamDefinitionException e) {
      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 checkForParseError(String name, String stream, XDDSLMessages msg, int pos, String... inserts) {
    try {
      StreamNode sn = parse(name, stream);
      fail("expected to fail but parsed " + sn.stringify());
    }
    catch (StreamDefinitionException e) {
      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 static class TestRepository implements CrudRepository<StreamDefinition, String> {

    private final static boolean debugRepository = false;

    private Map<String, StreamDefinition> data = new HashMap<String, StreamDefinition>();

    public void reset() {
      data.clear();
    }

    @Override
    public <S extends StreamDefinition> S save(S entity) {
      if (debugRepository) {
        System.out.println(System.identityHashCode(this) + " save(" + entity + ")");
      }
      data.put(entity.getName(), entity);
      return entity;
    }

    @Override
    public <S extends StreamDefinition> Iterable<S> save(Iterable<S> entities) {
      throw new IllegalStateException();
    }

    @Override
    public StreamDefinition findOne(String id) {
      StreamDefinition sd = data.get(id);
      if (debugRepository) {
        System.out.println(System.identityHashCode(this) + " repository findOne(" + id + ") returning " + sd);
      }
      return sd;
    }

    @Override
    public boolean exists(String id) {
      throw new IllegalStateException();
    }

    @Override
    public Iterable<StreamDefinition> findAll() {
      return data.values();
    }

    @Override
    public Iterable<StreamDefinition> findAll(Iterable<String> ids) {
      throw new IllegalStateException();
    }

    @Override
    public long count() {
      throw new IllegalStateException();
    }

    @Override
    public void delete(String id) {
      throw new IllegalStateException();
    }

    @Override
    public void delete(StreamDefinition entity) {
      throw new IllegalStateException();
    }

    @Override
    public void delete(Iterable<? extends StreamDefinition> entities) {
      throw new IllegalStateException();
    }

    @Override
    public void deleteAll() {
      throw new IllegalStateException();
    }

  }
}
TOP

Related Classes of org.springframework.xd.dirt.stream.dsl.StreamConfigParserTests

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.