/*
* 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();
}
}
}