summaryrefslogtreecommitdiffstats
path: root/config/src/test
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config/src/test
Publish
Diffstat (limited to 'config/src/test')
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/AppService.java76
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/BasicTest.java31
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java315
-rwxr-xr-xconfig/src/test/java/com/yahoo/config/subscription/ConfigApiTest.java60
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java111
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java188
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializationTest.java257
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java229
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java119
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInstanceUtilTest.java172
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigInterruptedExceptionTest.java19
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java133
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigSetTest.java24
-rwxr-xr-xconfig/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java68
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigSourceTest.java45
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java100
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/ConfigURITest.java46
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/DefaultConfigTest.java66
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/FunctionTest.java259
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/GenericConfigSubscriberTest.java75
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/NamespaceTest.java19
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/UnicodeTest.java31
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/impl/FileConfigSubscriptionTest.java105
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/impl/JRTConfigRequesterTest.java356
-rw-r--r--config/src/test/java/com/yahoo/config/subscription/util/JsonHelper.java28
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java120
-rwxr-xr-xconfig/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java38
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java141
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java42
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java57
-rwxr-xr-xconfig/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java234
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java336
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java31
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java121
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigPayloadBuilderTest.java363
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java461
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/DefaultValueApplierTest.java100
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ErrorCodeTest.java35
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/ErrorTypeTest.java35
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/GenericConfigBuilderTest.java48
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java124
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/LZ4CompressionTest.java74
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/LZ4PayloadCompressorTest.java30
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java123
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/RequestValidationTest.java35
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/SlimeUtilsTest.java82
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/SourceTest.java166
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/TimingValuesTest.java24
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/buildergen/ConfigBuilderGeneratorTest.java51
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/classes/app.1.def8
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/classes/qr-logging.def34
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/classes/qr-templates.3.def142
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/classes/ranges.1.def5
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/classes/testfoobar.12.def925
-rwxr-xr-xconfig/src/test/java/com/yahoo/vespa/config/configsglobal/qr-logging.cfg44
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-templates.3.cfg111
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/configsglobal/testfoobar.12.cfg105
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/ConfigResponseTest.java66
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestBase.java286
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestFactoryTest.java143
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestV3Test.java78
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java85
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/SlimeTraceSerializerTest.java123
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/protocol/TraceTest.java61
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java288
-rw-r--r--config/src/test/java/com/yahoo/vespa/config/xml/whitespace-test.xml6
-rw-r--r--config/src/test/resources/configdefinitions/bar.def4
-rw-r--r--config/src/test/resources/configdefinitions/baz.def4
-rw-r--r--config/src/test/resources/configdefinitions/bootstrap.def6
-rw-r--r--config/src/test/resources/configdefinitions/foo.def7
-rw-r--r--config/src/test/resources/configdefinitions/foobar.def4
-rw-r--r--config/src/test/resources/configdefinitions/foodefault.def4
-rw-r--r--config/src/test/resources/configdefinitions/function-test.def74
-rw-r--r--config/src/test/resources/configdefinitions/motd.def84
-rw-r--r--config/src/test/resources/configdefinitions/my.def4
-rw-r--r--config/src/test/resources/configs/bar/app.cfg7
-rw-r--r--config/src/test/resources/configs/bar/string.cfg1
-rw-r--r--config/src/test/resources/configs/baz/app.1.cfg.new85
-rw-r--r--config/src/test/resources/configs/baz/app.cfg5
-rw-r--r--config/src/test/resources/configs/datastructures/config1.txt17
-rw-r--r--config/src/test/resources/configs/datastructures/config1_1.txt14
-rw-r--r--config/src/test/resources/configs/datastructures/config2.txt19
-rw-r--r--config/src/test/resources/configs/def-files-nogen/foo.bar.app.def8
-rw-r--r--config/src/test/resources/configs/def-files/app.def9
-rw-r--r--config/src/test/resources/configs/def-files/arraytypes.def12
-rw-r--r--config/src/test/resources/configs/def-files/chains-test.def43
-rw-r--r--config/src/test/resources/configs/def-files/datastructures.def12
-rw-r--r--config/src/test/resources/configs/def-files/defaulttest.def9
-rw-r--r--config/src/test/resources/configs/def-files/function-test.def89
-rwxr-xr-xconfig/src/test/resources/configs/def-files/int.def5
-rw-r--r--config/src/test/resources/configs/def-files/maptypes.def13
-rw-r--r--config/src/test/resources/configs/def-files/md5test.def27
-rw-r--r--config/src/test/resources/configs/def-files/namespace.def6
-rw-r--r--config/src/test/resources/configs/def-files/simpletypes.def12
-rw-r--r--config/src/test/resources/configs/def-files/specialtypes.def4
-rw-r--r--config/src/test/resources/configs/def-files/standard.def9
-rwxr-xr-xconfig/src/test/resources/configs/def-files/string.def5
-rw-r--r--config/src/test/resources/configs/def-files/structtypes.def22
-rw-r--r--config/src/test/resources/configs/def-files/test-nodefs.def17
-rw-r--r--config/src/test/resources/configs/def-files/test-nonstring.def10
-rw-r--r--config/src/test/resources/configs/def-files/test-reference.def5
-rw-r--r--config/src/test/resources/configs/def-files/testnamespace.def4
-rw-r--r--config/src/test/resources/configs/def-files/unicode.def6
-rw-r--r--config/src/test/resources/configs/foo/app.cfg6
-rw-r--r--config/src/test/resources/configs/foo/test-reference.cfg1
-rw-r--r--config/src/test/resources/configs/function-test/defaultvalues.txt47
-rw-r--r--config/src/test/resources/configs/function-test/defaultvalues.xml62
-rw-r--r--config/src/test/resources/configs/function-test/missingvalue.txt39
-rw-r--r--config/src/test/resources/configs/function-test/randomorder.txt56
-rw-r--r--config/src/test/resources/configs/function-test/variableaccess.txt85
-rw-r--r--config/src/test/resources/configs/illegal/app.cfg6
-rw-r--r--config/src/test/resources/configs/unicode/unicode.cfg2
112 files changed, 9283 insertions, 0 deletions
diff --git a/config/src/test/java/com/yahoo/config/subscription/AppService.java b/config/src/test/java/com/yahoo/config/subscription/AppService.java
new file mode 100644
index 00000000000..1f0fc43ed4e
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/AppService.java
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.AppConfig;
+import com.yahoo.vespa.config.TimingValues;
+
+/**
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ *
+ * Application that subscribes to config defined in app.def and
+ * generated code in AppConfig.java.
+ */
+public class AppService {
+ protected int timesConfigured = 0;
+
+ protected AppConfig config = null;
+ private final ConfigSubscriber subscriber;
+ protected final String configId;
+
+ final Thread configThread;
+ boolean stopThread = false;
+
+ public AppService(String configId, ConfigSourceSet csource) {
+ this(configId, csource, null);
+ }
+
+ public int timesConfigured() { return timesConfigured; }
+
+ public AppService(String configId, ConfigSourceSet csource, TimingValues timingValues) {
+ if (csource == null) throw new IllegalArgumentException("Config source cannot be null");
+ this.configId = configId;
+ subscriber = new ConfigSubscriber(csource);
+ ConfigHandle<AppConfig> temp;
+ if (timingValues == null) {
+ temp = subscriber.subscribe(AppConfig.class, configId);
+ } else {
+ temp = subscriber.subscribe(AppConfig.class, configId, csource, timingValues);
+ }
+ final ConfigHandle<AppConfig> handle = temp;
+ configThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!stopThread) {
+ boolean changed = subscriber.nextConfig(500);
+ if (changed) {
+ configure(handle.getConfig());
+ timesConfigured++;
+ }
+ }
+ }
+ });
+ subscriber.nextConfig(5000);
+ timesConfigured++;
+ configure(handle.getConfig());
+ configThread.setDaemon(true);
+ configThread.start();
+ }
+
+ public void configure(AppConfig config) {
+ this.config = config;
+ }
+
+ public void cancelSubscription() {
+ subscriber.close();
+ stopThread = true;
+ }
+
+ public AppConfig getConfig() {
+ return config;
+ }
+
+ public boolean isConfigured() {
+ return (timesConfigured > 0);
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/BasicTest.java b/config/src/test/java/com/yahoo/config/subscription/BasicTest.java
new file mode 100644
index 00000000000..0d59e9e7ee7
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/BasicTest.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.is;
+
+import com.yahoo.foo.AppConfig;
+
+import org.junit.Test;
+
+
+public class BasicTest {
+
+ @Test
+ public void testSubBasic() {
+ ConfigSubscriber s = new ConfigSubscriber();
+ ConfigHandle<AppConfig> h = s.subscribe(AppConfig.class, "raw:times 0");
+ s.nextConfig(0);
+ AppConfig c = h.getConfig();
+ assertThat(c.times(), is(0));
+ }
+
+ @Test
+ public void testSubBasicGeneration() {
+ ConfigSubscriber s = new ConfigSubscriber();
+ ConfigHandle<AppConfig> h = s.subscribe(AppConfig.class, "raw:times 2");
+ s.nextGeneration(0);
+ AppConfig c = h.getConfig();
+ assertThat(c.times(), is(2));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java b/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java
new file mode 100644
index 00000000000..55d1e5774a5
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java
@@ -0,0 +1,315 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.foo.FunctionTestConfig;
+import com.yahoo.config.InnerNode;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.vespa.config.ConfigPayload;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.yahoo.config.subscription.util.JsonHelper.assertJsonEquals;
+import static com.yahoo.config.subscription.util.JsonHelper.inputJson;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @author Vegard Sjonfjell
+ * @since 5.1
+ */
+public class CfgConfigPayloadBuilderTest {
+
+ @Test
+ public void createConfigPayload() {
+ final FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+
+ final String expectedJson = inputJson(
+ "{",
+ " 'double_val': '41.23',",
+ " 'refarr': [",
+ " ':parent:',",
+ " ':parent',",
+ " 'parent:'",
+ " ],",
+ " 'pathVal': 'src/test/resources/configs/def-files/function-test.def',",
+ " 'string_val': 'foo',",
+ " 'myStructMap': {",
+ " 'one': {",
+ " 'myString': 'bull',",
+ " 'anotherMap': {",
+ " 'anotherOne': {",
+ " 'anInt': '3',",
+ " 'anIntDef': '4'",
+ " }",
+ " },",
+ " 'myInt': '1',",
+ " 'myStringDef': 'bear',",
+ " 'myIntDef': '2'",
+ " }",
+ " },",
+ " 'boolarr': [",
+ " 'false'",
+ " ],",
+ " 'intMap': {",
+ " 'dotted.key': '3',",
+ " 'spaced key': '4',",
+ " 'two': '2',",
+ " 'one': '1'",
+ " },",
+ " 'int_val': '5',",
+ " 'stringarr': [",
+ " 'bar'",
+ " ],",
+ " 'enum_val': 'FOOBAR',",
+ " 'myarray': [",
+ " {",
+ " 'anotherarray': [",
+ " {",
+ " 'foo': '7'",
+ " }",
+ " ],",
+ " 'intval': '-5',",
+ " 'fileVal': 'file0',",
+ " 'refval': ':parent:',",
+ " 'myStruct': {",
+ " 'a': '1',",
+ " 'b': '2'",
+ " },",
+ " 'stringval': [",
+ " 'baah',",
+ " 'yikes'",
+ " ],",
+ " 'enumval': 'INNER'",
+ " },",
+ " {",
+ " 'anotherarray': [",
+ " {",
+ " 'foo': '2'",
+ " }",
+ " ],",
+ " 'intval': '5',",
+ " 'fileVal': 'file1',",
+ " 'refval': ':parent:',",
+ " 'myStruct': {",
+ " 'a': '-1',",
+ " 'b': '-2'",
+ " },",
+ " 'enumval': 'INNER'",
+ " }",
+ " ],",
+ " 'fileArr': [",
+ " 'bin'",
+ " ],",
+ " 'enumwithdef': 'BAR2',",
+ " 'bool_with_def': 'true',",
+ " 'enumarr': [",
+ " 'VALUES'",
+ " ],",
+ " 'pathMap': {",
+ " 'one': 'pom.xml'",
+ " },",
+ " 'long_with_def': '-9876543210',",
+ " 'double_with_def': '-12.0',",
+ " 'stringMap': {",
+ " 'double spaced key': 'third',",
+ " 'double.dotted.key': 'second',",
+ " 'one': 'first'",
+ " },",
+ " 'refwithdef': ':parent:',",
+ " 'stringwithdef': 'bar and foo',",
+ " 'doublearr': [",
+ " '2344.0',",
+ " '123.0'",
+ " ],",
+ " 'int_with_def': '-14',",
+ " 'pathArr': [",
+ " 'pom.xml'",
+ " ],",
+ " 'rootStruct': {",
+ " 'innerArr': [",
+ " {",
+ " 'stringVal': 'deep',",
+ " 'boolVal': 'true'",
+ " },",
+ " {",
+ " 'stringVal': 'blue a=\\\'escaped\\\'',",
+ " 'boolVal': 'false'",
+ " }",
+ " ],",
+ " 'inner0': {",
+ " 'index': '11',",
+ " 'name': 'inner0'",
+ " },",
+ " 'inner1': {",
+ " 'index': '12',",
+ " 'name': 'inner1'",
+ " }",
+ " },",
+ " 'fileVal': 'etc',",
+ " 'refval': ':parent:',",
+ " 'onechoice': 'ONLYFOO',",
+ " 'bool_val': 'false',",
+ " 'longarr': [",
+ " '9223372036854775807',",
+ " '-9223372036854775808'",
+ " ],",
+ " 'basicStruct': {",
+ " 'intArr': [",
+ " '310',",
+ " '311'",
+ " ],",
+ " 'foo': 'basicFoo',",
+ " 'bar': '3'",
+ " },",
+ " 'long_val': '12345678901'",
+ "}"
+ );
+
+ CfgConfigPayloadBuilderTest.assertDeserializedConfigEqualsJson(config, expectedJson);
+ }
+
+ // FIXME: We need to define the behavior here.
+ @Test
+ public void test_empty_struct_arrays() {
+ assertDeserializedConfigEqualsJson("myArray[1]", inputJson("{}"));
+ }
+
+ @Test
+ public void test_escaped_string() {
+ assertDeserializedConfigEqualsJson("a b=\"escaped\"",
+ inputJson(
+ "{",
+ " 'a': 'b=\\\'escaped\\\''",
+ "}"
+ )
+ );
+ }
+
+ @Test
+ public void test_empty_payload() {
+ assertDeserializedConfigEqualsJson("", inputJson("{}"));
+ }
+
+ @Test
+ public void test_leading_whitespace() {
+ assertDeserializedConfigEqualsJson(" a 0",
+ inputJson(
+ "{",
+ " 'a': '0'",
+ "}")
+ );
+ }
+
+ @Test
+ public void test_leading_and_trailing_whitespace_string() {
+ assertDeserializedConfigEqualsJson(
+ "a \" foo \"",
+ inputJson(
+ "{",
+ " 'a': ' foo '",
+ "}"));
+ }
+
+ @Test
+ public void test_config_with_comments() {
+ CfgConfigPayloadBuilderTest.assertDeserializedConfigEqualsJson(
+ Arrays.asList(
+ "fielda b\n",
+ "#fielda c\n",
+ "#fieldb c\n",
+ "# just a comment"),
+ inputJson(
+ "{",
+ " 'fielda': 'b'",
+ "}")
+ );
+ }
+
+ @Test
+ public void testConvertingMaps() {
+ List<String> payload = Arrays.asList(
+ "intmap{\"foo\"} 1337",
+ "complexmap{\"key\"}.foo 1337",
+ "nestedmap{\"key1\"}.foo{\"key2\"}.bar 1337"
+ );
+
+ final String expectedJson = inputJson(
+ "{",
+ " 'nestedmap': {",
+ " 'key1': {",
+ " 'foo': {",
+ " 'key2': {",
+ " 'bar': '1337'",
+ " }",
+ " }",
+ " }",
+ " },",
+ " 'intmap': {",
+ " 'foo': '1337'",
+ " },",
+ " 'complexmap': {",
+ " 'key': {",
+ " 'foo': '1337'",
+ " }",
+ " }",
+ "}"
+ );
+
+ CfgConfigPayloadBuilderTest.assertDeserializedConfigEqualsJson(payload, expectedJson);
+ }
+
+ @Test
+ public void createConfigPayloadUtf8() {
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder().stringval("Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo");
+ SimpletypesConfig config = new SimpletypesConfig(builder);
+
+ String expectedJson = inputJson(
+ "{",
+ " 'longval': '0',",
+ " 'intval': '0',",
+ " 'stringval': 'Hei \u00e6\u00f8\u00e5 \ubc14\ub451 \u00c6\u00d8\u00c5 hallo',",
+ " 'boolval': 'false',",
+ " 'doubleval': '0.0',",
+ " 'enumval': 'VAL1'",
+ "}"
+ );
+
+ CfgConfigPayloadBuilderTest.assertDeserializedConfigEqualsJson(config, expectedJson);
+ }
+
+ @Test
+ public void testLineParsing() {
+ CfgConfigPayloadBuilder builder = new CfgConfigPayloadBuilder();
+ assertEquals(builder.parseFieldAndValue("foo bar").getFirst(), "foo");
+ assertEquals(builder.parseFieldAndValue("foo bar").getSecond(), "bar");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my key} baR").getFirst(), "foo.bar.baz{my key}");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my key} baR").getSecond(), "baR");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my.key} baRR").getFirst(), "foo.bar.baz{my.key}");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my.key} baRR").getSecond(), "baRR");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my key.dotted}.biz baRO").getFirst(), "foo.bar.baz{my key.dotted}.biz");
+ assertEquals(builder.parseFieldAndValue("foo.bar.baz{my key.dotted}.biz baRO").getSecond(), "baRO");
+
+ assertEquals(builder.parseFieldList("foo.bar.baz").get(0), "foo");
+ assertEquals(builder.parseFieldList("foo.bar.baz").get(1), "bar");
+ assertEquals(builder.parseFieldList("foo.bar.baz").get(2), "baz");
+ assertEquals(builder.parseFieldList("foo.bar{f.b}.baz{f.h h}").get(0), "foo");
+ assertEquals(builder.parseFieldList("foo.bar{f.b}.baz{f.h h}").get(1), "bar{f.b}");
+ assertEquals(builder.parseFieldList("foo.bar{f.b}.baz{f.h h}").get(2), "baz{f.h h}");
+ }
+
+ private static void assertDeserializedConfigEqualsJson(InnerNode config, String expectedJson) {
+ assertDeserializedConfigEqualsJson(ConfigInstance.serialize(config), expectedJson);
+ }
+
+ private static void assertDeserializedConfigEqualsJson(String serializedConfig, String expectedJson) {
+ assertDeserializedConfigEqualsJson(Arrays.asList(serializedConfig), expectedJson);
+ }
+
+ private static void assertDeserializedConfigEqualsJson(List<String> inputConfig, String expectedJson) {
+ ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(inputConfig);
+ assertJsonEquals(payload.toString(), expectedJson);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigApiTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigApiTest.java
new file mode 100755
index 00000000000..608bf2e12b0
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigApiTest.java
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.foo.*;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.is;
+
+/**
+ * Tests ConfigSubscriber API, and the ConfigHandle class.
+ *
+ * @author <a href="gv@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1
+ */
+public class ConfigApiTest {
+ private static final String CONFIG_ID = "raw:" + "times 1\n";
+
+ @Test
+ public void testConfigSubscriber() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigHandle<AppConfig> h = subscriber.subscribe(AppConfig.class, CONFIG_ID);
+ assertNotNull(h);
+ subscriber.nextConfig();
+ assertNotNull(h.getConfig());
+ assertEquals(AppConfig.CONFIG_DEF_NAME, ConfigInstance.getDefName(h.getConfig().getClass()));
+ assertThat(h.isChanged(), is(true));
+ assertTrue(h.toString().startsWith("Handle changed: true\nSub:\n"));
+ subscriber.close();
+ assertThat(subscriber.state(), is(ConfigSubscriber.State.CLOSED));
+ }
+
+ /**
+ * Verifies that we get an exception when trying to subscribe after close() has been called
+ * for a ConfigSubscriber
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSubscribeAfterClose() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ subscriber.subscribe(AppConfig.class, CONFIG_ID);
+ subscriber.nextConfig();
+ subscriber.close();
+ subscriber.subscribe(AppConfig.class, CONFIG_ID);
+ }
+
+ /**
+ * Verifies that it is not possible to to subscribe again after calling nextConfig()
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSubscribeAfterNextConfig() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ subscriber.subscribe(AppConfig.class, CONFIG_ID);
+ subscriber.nextConfig();
+ subscriber.subscribe(AppConfig.class, CONFIG_ID);
+ subscriber.close();
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java
new file mode 100644
index 00000000000..bb5e712fe3a
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigGetterTest.java
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.AppConfig;
+
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit test for the {@link ConfigGetter}.
+ *
+ * @author gjoranv
+ */
+public class ConfigGetterTest {
+ private ConfigSourceSet sourceSet = new ConfigSourceSet("config-getter-test");
+
+ @Test
+ public void testGetConfig() {
+ int times = 11;
+ String message = "testGetConfig";
+ String a0 = "a0";
+ String configId = "raw:times " + times + "\nmessage " + message + "\na[1]\na[0].name " + a0;
+
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(AppConfig.class);
+ AppConfig config = getter.getConfig(configId);
+ assertThat(config.times(), is(times));
+ assertThat(config.message(), is(message));
+ assertThat(config.a().size(), is(1));
+ assertThat(config.a(0).name(), is(a0));
+
+ AppService service = new AppService(configId, sourceSet);
+ AppConfig serviceConfig = service.getConfig();
+ assertTrue(service.isConfigured());
+ assertThat(config, is(serviceConfig));
+ }
+
+@Test
+ public void testGetFromRawSource() {
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(new RawSource("message \"one\""), AppConfig.class);
+ AppConfig config = getter.getConfig("test");
+ assertThat(config.message(), is("one"));
+ }
+
+ @Test
+ public void testGetTwice() {
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(AppConfig.class);
+ AppConfig config = getter.getConfig("raw:message \"one\"");
+ assertThat(config.message(), is("one"));
+ config = getter.getConfig("raw:message \"two\"");
+ assertThat(config.message(), is("two"));
+ }
+
+ @Test
+ public void testGetFromFile() {
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(AppConfig.class);
+ AppConfig config = getter.getConfig("file:src/test/resources/configs/foo/app.cfg");
+ verifyFooValues(config);
+ }
+
+ @Test
+ public void testGetFromFileSource() {
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(new FileSource(new File("src/test/resources/configs/foo/app.cfg")), AppConfig.class);
+ AppConfig config = getter.getConfig("test");
+ verifyFooValues(config);
+ }
+
+ @Test
+ public void testGetFromDir() {
+ ConfigGetter<AppConfig> getter = new ConfigGetter<>(AppConfig.class);
+ AppConfig config = getter.getConfig("dir:src/test/resources/configs/foo/");
+ verifyFooValues(config);
+ }
+
+ @Test
+ public void testGetFromDirSource() {
+ AppConfig config = ConfigGetter.getConfig(AppConfig.class, "test", new DirSource(new File("src/test/resources/configs/foo/")));
+ verifyFooValues(config);
+ }
+
+ private void verifyFooValues(AppConfig config) {
+ assertThat(config.message(), is("msg1"));
+ assertThat(config.times(), is(3));
+ assertThat(config.a(0).name(), is("a0"));
+ assertThat(config.a(1).name(), is("a1"));
+ assertThat(config.a(2).name(), is("a2"));
+ }
+
+ @Test
+ public void testsStaticGetConfig() {
+ int times = 11;
+ String message = "testGetConfig";
+ String a0 = "a0";
+ String configId = "raw:times " + times + "\nmessage " + message + "\na[1]\na[0].name " + a0;
+
+ AppConfig config = ConfigGetter.getConfig(AppConfig.class, configId);
+ assertThat(config.times(), is(times));
+ assertThat(config.message(), is(message));
+ assertThat(config.a().size(), is(1));
+ assertThat(config.a(0).name(), is(a0));
+
+ AppService service = new AppService(configId, sourceSet);
+ AppConfig serviceConfig = service.getConfig();
+ assertTrue(service.isConfigured());
+ assertThat(config, is(serviceConfig));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java
new file mode 100644
index 00000000000..d7a88d591e4
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java
@@ -0,0 +1,188 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.FileReference;
+import com.yahoo.foo.FunctionTestConfig;
+import com.yahoo.foo.MaptypesConfig;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.ConfigTransformer;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.yahoo.foo.FunctionTestConfig.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * @author gjoranv
+ * @since 5.1.6
+ */
+public class ConfigInstancePayloadTest {
+
+ static FunctionTestConfig createVariableAccessConfigWithBuilder() {
+ return new FunctionTestConfig(createVariableAccessBuilder());
+ }
+
+ static FunctionTestConfig.Builder createVariableAccessBuilder() {
+ return new FunctionTestConfig.Builder().
+ bool_val(false).
+ bool_with_def(true).
+ int_val(5).
+ int_with_def(-14).
+ long_val(12345678901L).
+ long_with_def(-9876543210L).
+ double_val(41.23).
+ double_with_def(-12).
+ string_val("foo").
+ stringwithdef("bar and foo").
+ enum_val(Enum_val.FOOBAR).
+ enumwithdef(Enumwithdef.BAR2).
+ refval(":parent:").
+ refwithdef(":parent:").
+ fileVal("etc").
+ pathVal(FileReference.mockFileReferenceForUnitTesting(new File("src/test/resources/configs/def-files/function-test.def"))).
+ boolarr(false).
+ longarr(9223372036854775807L).
+ longarr(-9223372036854775808L).
+ doublearr(2344.0).
+ doublearr(123.0).
+ stringarr("bar").
+ enumarr(Enumarr.VALUES).
+ refarr(Arrays.asList(":parent:", ":parent", "parent:")). // test collection based setter
+ fileArr("bin").
+ pathArr(FileReference.mockFileReferenceForUnitTesting(new File("pom.xml"))).
+ intMap("one", 1).
+ intMap("two", 2).
+ intMap("dotted.key", 3).
+ intMap("spaced key", 4).
+ stringMap("one", "first").
+ stringMap("double.dotted.key", "second").
+ stringMap("double spaced key", "third").
+ pathMap("one", FileReference.mockFileReferenceForUnitTesting(new File("pom.xml"))).
+ basicStruct(new BasicStruct.Builder().
+ foo("basicFoo").
+ bar(3).
+ intArr(310).intArr(311)).
+
+ rootStruct(new RootStruct.Builder().
+ inner0(new RootStruct.Inner0.Builder().
+ index(11)).
+ inner1(new RootStruct.Inner1.Builder().
+ index(12)).
+ innerArr(new RootStruct.InnerArr.Builder().
+ boolVal(true).
+ stringVal("deep")).
+ innerArr(new RootStruct.InnerArr.Builder().
+ boolVal(false).
+ stringVal("blue a=\"escaped\""))).
+
+ myarray(new Myarray.Builder().
+ intval(-5).
+ stringval("baah").
+ stringval("yikes").
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file0").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(7)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(1).
+ b(2))).
+
+ myarray(new Myarray.Builder().
+ intval(5).
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file1").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(1).
+ foo(2)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(-1).
+ b(-2))).
+
+ myStructMap("one", new MyStructMap.Builder().
+ myInt(1).
+ myString("bull").
+ myIntDef(2).
+ myStringDef("bear").
+ anotherMap("anotherOne", new MyStructMap.AnotherMap.Builder().
+ anInt(3).
+ anIntDef(4)));
+ }
+
+ @Test
+ public void config_builder_can_be_created_from_generic_payload() {
+ FunctionTestConfig config = createVariableAccessConfigWithBuilder();
+ ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(ConfigInstance.serialize(config));
+ assertFunctionTestPayload(config, payload);
+ }
+
+ @Test
+ public void config_builder_can_be_created_from_typed_payload() {
+ FunctionTestConfig config = createVariableAccessConfigWithBuilder();
+ Slime slime = new Slime();
+ ConfigInstanceSerializer serializer = new ConfigInstanceSerializer(slime);
+ ConfigInstance.serialize(config, serializer);
+ assertFunctionTestPayload(config, new ConfigPayload(slime));
+ }
+
+ private void assertFunctionTestPayload(FunctionTestConfig expected, ConfigPayload payload) {
+ try {
+ System.out.println(payload.toString(false));
+ FunctionTestConfig config2 = new FunctionTestConfig((FunctionTestConfig.Builder)new ConfigTransformer<>(FunctionTestConfig.class).toConfigBuilder(payload));
+ assertThat(config2, is(expected));
+ assertThat(ConfigInstance.serialize(config2), is(ConfigInstance.serialize(expected)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
+
+ @Test
+ public void function_test_payload_is_correctly_deserialized() {
+ FunctionTestConfig orig = createVariableAccessConfigWithBuilder();
+ List<String> lines = ConfigInstance.serialize(orig);
+ ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(lines);
+ FunctionTestConfig config = ConfigInstanceUtil.getNewInstance(FunctionTestConfig.class, "foo", payload);
+ FunctionTest.assertVariableAccessValues(config, "foo");
+ }
+
+ @Test
+ public void map_types_are_correctly_deserialized() {
+ MaptypesConfig orig = createMapTypesConfig();
+ List<String> lines = ConfigInstance.serialize(orig);
+ ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(lines);
+ System.out.println(payload.toString());
+ MaptypesConfig config = ConfigInstanceUtil.getNewInstance(MaptypesConfig.class, "foo", payload);
+ System.out.println(config);
+ assertThat(config.intmap().size(), is(1));
+ assertThat(config.intmap("foo"), is(1337));
+ assertNotNull(config.innermap("bar"));
+ assertThat(config.innermap("bar").foo(), is(93));
+ assertThat(config.nestedmap().size(), is(1));
+ assertNotNull(config.nestedmap("baz"));
+ assertThat(config.nestedmap("baz").inner("foo"), is(1));
+ assertThat(config.nestedmap("baz").inner("bar"), is(2));
+ }
+
+ private MaptypesConfig createMapTypesConfig() {
+ MaptypesConfig.Builder builder = new MaptypesConfig.Builder();
+ builder.intmap("foo", 1337);
+ MaptypesConfig.Innermap.Builder inner = new MaptypesConfig.Innermap.Builder();
+ inner.foo(93);
+ builder.innermap("bar", inner);
+ MaptypesConfig.Nestedmap.Builder n1 = new MaptypesConfig.Nestedmap.Builder();
+ n1.inner("foo", 1);
+ n1.inner("bar", 2);
+ builder.nestedmap("baz", n1);
+ return new MaptypesConfig(builder);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializationTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializationTest.java
new file mode 100644
index 00000000000..97e954b95fb
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializationTest.java
@@ -0,0 +1,257 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.foo.FunctionTestConfig;
+import com.yahoo.config.codegen.DefLine;
+import com.yahoo.vespa.config.ConfigPayload;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ * @author vegardh
+ */
+public class ConfigInstanceSerializationTest {
+
+ private DefLine.Type stringType = new DefLine.Type("string");
+ private DefLine.Type intType = new DefLine.Type("int");
+ private DefLine.Type longType = new DefLine.Type("long");
+ private DefLine.Type boolType = new DefLine.Type("bool");
+ private DefLine.Type doubleType = new DefLine.Type("double");
+ private DefLine.Type fileType = new DefLine.Type("file");
+ private DefLine.Type refType = new DefLine.Type("reference");
+
+ @Test
+ public void require_symmetrical_serialization_and_deserialization_with_builder() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+
+ // NOTE: configId must be ':parent:' because the library replaces ReferenceNodes with that value with
+ // the instance's configId. (And the config used here contains such nodes.)
+ List<String> lines = ConfigInstance.serialize(config);
+ ConfigPayload payload = new CfgConfigPayloadBuilder().deserialize(lines);
+
+ FunctionTestConfig config2 = ConfigInstanceUtil.getNewInstance(FunctionTestConfig.class, ":parent:", payload);
+ assertThat(config, is(config2));
+ assertThat(ConfigInstance.serialize(config), is(ConfigInstance.serialize(config2)));
+ }
+
+/** Looks like everything in the commented block tests unused api's.. remove?
+
+ @Test
+ public void testSerializeAgainstConfigDefinitionAllowNothing() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+ InnerCNode def = new InnerCNode("function-test");
+ List<String> payload = Validator.serialize(config, def);
+ Assert.assertEquals(payload.size(), 0);
+ }
+
+ @Test
+ public void testSerializeAgainstConfigDefinitionAllLeaves() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+ InnerCNode def = new InnerCNode("function-test");
+ def.children().put("bool_val", LeafCNode.newInstance(boolType, def, "bool_val"));
+ def.children().put("bool_with_def", LeafCNode.newInstance(boolType, def, "bool_with_def"));
+ def.children().put("int_val", LeafCNode.newInstance(intType, def, "int_val"));
+ def.children().put("int_with_def", LeafCNode.newInstance(intType, def, "int_with_def"));
+ def.children().put("long_val", LeafCNode.newInstance(longType, def, "long_val"));
+ def.children().put("long_with_def", LeafCNode.newInstance(longType, def, "long_with_def"));
+ def.children().put("double_val", LeafCNode.newInstance(doubleType, def, "double_val"));
+ def.children().put("double_with_def", LeafCNode.newInstance(doubleType, def, "double_with_def"));
+ def.children().put("string_val", LeafCNode.newInstance(stringType, def, "string_val"));
+ def.children().put("stringwithdef", LeafCNode.newInstance(stringType, def, "stringwithdef"));
+ def.children().put("enum_val", LeafCNode.newInstance(enumType(new String[]{"FOO", "BAR", "FOOBAR"}), def, "enum_val"));
+ def.children().put("enumwithdef", LeafCNode.newInstance(enumType(new String[]{"FOO2", "BAR2", "FOOBAR2"}), def, "enumwithdef", "BAR2"));
+ def.children().put("refval", LeafCNode.newInstance(refType, def, "refval"));
+ def.children().put("refwithdef", LeafCNode.newInstance(refType, def, "refwithdef"));
+ def.children().put("fileVal", LeafCNode.newInstance(fileType, def, "fileVal"));
+
+ List<String> payload = Validator.serialize(config, def);
+ String plString = payload.toString();
+ Assert.assertTrue(plString.matches(".*bool_val false.*"));
+ Assert.assertTrue(plString.matches(".*bool_with_def true.*"));
+ Assert.assertTrue(plString.matches(".*int_val 5.*"));
+ Assert.assertTrue(plString.matches(".*int_with_def -14.*"));
+ Assert.assertTrue(plString.matches(".*long_val 12345678901.*"));
+ Assert.assertTrue(plString.matches(".*long_with_def -9876543210.*"));
+ Assert.assertTrue(plString.matches(".*double_val 41\\.23.*"));
+ Assert.assertTrue(plString.matches(".*double_with_def -12.*"));
+ Assert.assertTrue(plString.matches(".*string_val \"foo\".*"));
+ Assert.assertTrue(plString.matches(".*stringwithdef \"bar and foo\".*"));
+ Assert.assertTrue(plString.matches(".*enum_val FOOBAR.*"));
+ Assert.assertTrue(plString.matches(".*enumwithdef BAR2.*"));
+ Assert.assertTrue(plString.matches(".*refval \\:parent\\:.*"));
+ Assert.assertTrue(plString.matches(".*refwithdef \\:parent\\:.*"));
+ Assert.assertTrue(plString.matches(".*fileVal \"etc\".*"));
+ }
+
+ @Test
+ public void testSerializeAgainstConfigDefinitionSomeLeaves() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+ InnerCNode def = new InnerCNode("function-test");
+ def.children().put("stringwithdef", LeafCNode.newInstance(stringType, def, "stringwithdef"));
+ def.children().put("long_with_def", LeafCNode.newInstance(longType, def, "long_with_def"));
+ // But not double_with_def etc, and no structs/arrays
+ List<String> payload = Validator.serialize(config, def);
+ String plString = payload.toString();
+ Assert.assertTrue(plString.matches(".*long_with_def \\-9876543210.*"));
+ Assert.assertTrue(plString.matches(".*stringwithdef \"bar and foo\".*"));
+ Assert.assertFalse(plString.matches(".*double_with_def.*"));
+ Assert.assertFalse(plString.matches(".*fileVal \"etc\".*"));
+ Assert.assertFalse(plString.matches(".*basicStruct.*"));
+ }
+
+ @Test
+ public void testSerializationAgainstConfigDefinitionAddedValsInDef() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+ InnerCNode def = new InnerCNode("function-test");
+ def.children().put("stringwithdef", LeafCNode.newInstance(stringType, def, "stringwithdef"));
+ def.children().put("someotherstring", LeafCNode.newInstance(stringType, def, "someotherstring", "some other"));
+ def.children().put("long_with_def", LeafCNode.newInstance(longType, def, "long_with_def"));
+ def.children().put("some_other_long", LeafCNode.newInstance(longType, def, "some_other_long", "88"));
+ def.children().put("some_other_enum", LeafCNode.newInstance(enumType(new String[]{"hey", "ho", "lets", "go"}), def, "some_other_enum", "lets"));
+ def.children().put("int_val_nofdef", LeafCNode.newInstance(intType, def, "int_val_nodef", null));
+
+ // But not double_with_def etc, and no structs/arrays
+ List<String> payload = Validator.serialize(config, def);
+ String plString = payload.toString();
+ Assert.assertTrue(plString.matches(".*long_with_def \\-9876543210.*"));
+ Assert.assertTrue(plString.matches(".*stringwithdef \"bar and foo\".*"));
+ Assert.assertTrue(plString.matches(".*.someotherstring \"some other\".*"));
+ Assert.assertTrue(plString.matches(".*some_other_long 88.*"));
+ Assert.assertTrue(plString.matches(".*some_other_enum lets.*"));
+ Assert.assertFalse(plString.matches(".*double_with_def.*"));
+ Assert.assertFalse(plString.matches(".*fileVal \"etc\".*"));
+ Assert.assertFalse(plString.matches(".*basicStruct.*"));
+ Assert.assertFalse(plString.matches(".*int_val_nodef.*"));
+ }
+
+ @Test
+ public void testSerializeAgainstConfigDefinitionMismatchAllWays() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+
+ // Create all sorts of mismatches in the def schema used to serialize
+ InnerCNode def = new InnerCNode("function-test");
+ def.children().put("long_with_def", LeafCNode.newInstance(longType, def, "long_with_def"));
+ def.children().put("stringwithdef", LeafCNode.newInstance(intType, def, "stringwithdef"));
+ def.children().put("basicStruct", LeafCNode.newInstance(intType, def, "basicStruct"));
+ InnerCNode doubleValWrong = new InnerCNode("double_val");
+ doubleValWrong.children().put("foo", LeafCNode.newInstance(intType, def, "foo"));
+ def.children().put("double_val", doubleValWrong);
+ InnerCNode myArray = new InnerCNode("myarray");
+ myArray.children().put("intval", LeafCNode.newInstance(stringType, myArray, "foo"));
+ InnerCNode myStruct = new InnerCNode("myStruct");
+ myStruct.children().put("a", LeafCNode.newInstance(stringType, myStruct, "foo"));
+ myArray.children().put("myStruct", myStruct);
+ def.children().put("myarray", myArray);
+
+ List<String> payload = Validator.serialize(config, def);
+ String plString = payload.toString();
+ Assert.assertTrue(plString.matches(".*long_with_def.*"));
+ Assert.assertFalse(plString.matches(".*stringwithdef.*"));
+ Assert.assertFalse(plString.matches(".*basicStruct.*"));
+ Assert.assertFalse(plString.matches(".*double_val.*"));
+ Assert.assertFalse(plString.matches(".*intval.*"));
+ Assert.assertFalse(plString.matches(".*\\.a.*"));
+ }
+
+ @Test
+ public void testSerializeAgainstConfigDefinitionComplex() {
+ FunctionTestConfig config = ConfigInstancePayloadTest.createVariableAccessConfigWithBuilder();
+
+ // Build a pretty complex def programatically
+ InnerCNode def = new InnerCNode("function-test");
+ def.children().put("stringwithdef", LeafCNode.newInstance(stringType, def, "stringwithdef"));
+ def.children().put("someUnknownStringNoDefault", LeafCNode.newInstance(stringType, def, "someUnknownStringNoDefault"));
+ InnerCNode basicStruct = new InnerCNode("basicStruct");
+ basicStruct.children().put("foo", LeafCNode.newInstance(stringType, def, "foo")); // but not bar
+ InnerCNode rootStruct = new InnerCNode("rootStruct");
+ InnerCNode inner1 = new InnerCNode("inner1");
+ InnerCNode someUnknwonStruct = new InnerCNode("someUnknownStruct");
+ InnerCNode someUnknownInner = new InnerCNode("someUnknownInner");
+ InnerCNode innerArr = new InnerCNode("innerArr");
+ rootStruct.children().put("inner1", inner1);
+ rootStruct.children().put("someUnknownStruct", someUnknwonStruct);
+ rootStruct.children().put("someUnknownInner", someUnknownInner);
+ rootStruct.children().put("innerArr", innerArr);
+ InnerCNode myarray = new InnerCNode("myarray");
+ InnerCNode unknownInner = new InnerCNode("unknownInner");
+ def.children().put("basicStruct", basicStruct);
+ def.children().put("rootStruct", rootStruct);
+ def.children().put("myarray", myarray);
+ def.children().put("unknownInner", unknownInner);
+ inner1.children().put("index", LeafCNode.newInstance(intType, inner1, "index"));
+ inner1.children().put("someUnknownInt", LeafCNode.newInstance(intType, inner1, "someUnknownInt", "-98"));
+ inner1.children().put("someUnknownIntNoDefault", LeafCNode.newInstance(intType, inner1, "someUnknownIntNoDefault"));
+ inner1.children().put("someUnknownEnum", LeafCNode.newInstance(enumType(new String[]{"goo", "go", "gorilla"}), inner1, "someUnknownEnum", "go"));
+ inner1.children().put("someUnknownEnumNoDefault", LeafCNode.newInstance(enumType(new String[]{"foo", "bar", "baz"}), inner1, "someUnknownEnumNoDefault"));
+ someUnknwonStruct.children().put("anint", LeafCNode.newInstance(intType, someUnknwonStruct, "anint", "3"));// But no instances of this in config
+ someUnknownInner.children().put("along", LeafCNode.newInstance(longType, someUnknownInner, "along", "234"));// No instance in config
+ innerArr.children().put("boolVal", LeafCNode.newInstance(boolType, innerArr, "boolVal"));
+ innerArr.children().put("someUnknownDouble", LeafCNode.newInstance(doubleType, innerArr, "someUnknownDouble", "-675.789"));
+ innerArr.children().put("someUnknownDoubleNoDefault", LeafCNode.newInstance(doubleType, innerArr, "someUnknownDoubleNoDefault"));
+ myarray.children().put("fileVal", LeafCNode.newInstance(fileType, myarray, "fileVal"));
+ myarray.children().put("stringval", new InnerCNode("stringval[]"));
+ // TODO make sure default for file is not allowed
+ //myarray.children().put("someUnknownFile", LeafCNode.newInstance(fileType, myarray, "someUnknownFile", "opt/"));
+ unknownInner.children().put("aDouble", LeafCNode.newInstance(doubleType, unknownInner, "aDouble", "1234"));
+ def.children().put("longarr", new InnerCNode("longarr[]"));
+ def.children().put("boolarr", new InnerCNode("boolarr[]"));
+ def.children().put("doublearr", new InnerCNode("doublearr[]"));
+ def.children().put("stringarr", new InnerCNode("stringarr[]"));
+ def.children().put("fileArr", new InnerCNode("fileArr[]"));
+ def.children().put("refarr", new InnerCNode("refarr[]"));
+ def.children().put("enumarr", new InnerCNode("enumarr[]"));
+ List<String> payload = Validator.serialize(config, def);
+ String plString = payload.toString();
+ Assert.assertFalse(plString.matches(".*long_with_def \\-9876543210.*"));
+ Assert.assertFalse(plString.matches(".*someUnknownStringNoDefault.*"));
+ Assert.assertTrue(plString.matches(".*stringwithdef \"bar and foo\".*"));
+ Assert.assertFalse(plString.matches(".*double_with_def.*"));
+ Assert.assertFalse(plString.matches(".*fileVal etc.*"));
+ Assert.assertTrue(plString.matches(".*basicStruct\\.foo \"basicFoo\".*"));
+ Assert.assertFalse(plString.matches(".*basicStruct\\.bar.*"));
+ Assert.assertFalse(plString.matches(".*rootStruct\\.inner0.*"));
+ Assert.assertFalse(plString.matches(".*unknownInner.*"));
+ Assert.assertFalse(plString.matches(".*rootStruct\\.someUnknownStruct.*"));
+ Assert.assertFalse(plString.matches(".*rootStruct\\.someUnknownInner.*"));
+ Assert.assertFalse(plString.matches(".*rootStruct\\.inner1\\.name.*"));
+ Assert.assertTrue(plString.matches(".*rootStruct\\.inner1\\.index 12.*"));
+ Assert.assertTrue(plString.matches(".*rootStruct\\.inner1\\.someUnknownInt -98.*"));
+ Assert.assertTrue(plString.matches(".*rootStruct\\.inner1\\.someUnknownEnum go.*"));
+ Assert.assertTrue(plString.matches(".*rootStruct\\.innerArr\\[0\\]\\.boolVal true.*"));
+ Assert.assertFalse(plString.matches(".*someUnknownEnumNoDefault.*"));
+ Assert.assertFalse(plString.matches(".*someUnknownDoubleNoDefault.*"));
+ Assert.assertFalse(plString.matches(".*someUnknownIntNoDefault.*"));
+ Assert.assertTrue(plString.matches(".*rootStruct\\.innerArr\\[0\\]\\.someUnknownDouble -675.789.*"));
+ Assert.assertFalse(plString.matches(".*rootStruct\\.innerArr\\[0\\]\\.stringVal*"));
+ Assert.assertFalse(plString.matches(".*myarray\\[0\\].intval.*"));
+ Assert.assertTrue(plString.matches(".*myarray\\[0\\].fileVal \"file0\".*"));
+ //assertTrue(plString.matches(".*myarray\\[0\\].someUnknownFile \"opt/\".*"));
+ Assert.assertTrue(plString.matches(".*myarray\\[0\\].stringval\\[0\\] \"baah\".*"));
+ Assert.assertTrue(plString.matches(".*myarray\\[0\\].stringval\\[1\\] \"yikes\".*"));
+ Assert.assertTrue(plString.matches(".*myarray\\[1\\].fileVal \"file1\".*"));
+ Assert.assertFalse(plString.matches(".*myarray\\[1\\].enumVal.*"));
+ Assert.assertFalse(plString.matches(".*myarray\\[1\\].refVal.*"));
+ Assert.assertTrue(plString.matches(".*boolarr\\[0\\] false.*"));
+ Assert.assertTrue(plString.matches(".*longarr\\[0\\] 9223372036854775807.*"));
+ Assert.assertTrue(plString.matches(".*longarr\\[1\\] -9223372036854775808.*"));
+ Assert.assertTrue(plString.matches(".*doublearr\\[0\\] 2344\\.0.*"));
+ Assert.assertTrue(plString.matches(".*doublearr\\[1\\] 123\\.0.*"));
+ Assert.assertTrue(plString.matches(".*stringarr\\[0\\] \"bar\".*"));
+ Assert.assertTrue(plString.matches(".*enumarr\\[0\\] VALUES.*"));
+ Assert.assertTrue(plString.matches(".*refarr\\[0\\] \\:parent\\:.*"));
+ Assert.assertTrue(plString.matches(".*refarr\\[1\\] \\:parent.*"));
+ Assert.assertTrue(plString.matches(".*refarr\\[2\\] parent\\:.*"));
+ Assert.assertTrue(plString.matches(".*fileArr\\[0\\] \"bin\".*"));
+ }
+
+ private DefLine.Type enumType(String[] strings) {
+ return new DefLine.Type("enum").setEnumArray(strings);
+ }
+**/
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java
new file mode 100644
index 00000000000..d3713eaa401
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceSerializerTest.java
@@ -0,0 +1,229 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.ArraytypesConfig;
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.SpecialtypesConfig;
+import com.yahoo.foo.StructtypesConfig;
+import com.yahoo.foo.MaptypesConfig;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import static com.yahoo.config.subscription.util.JsonHelper.assertJsonEquals;
+import static com.yahoo.config.subscription.util.JsonHelper.inputJson;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ * @author Vegard Sjonfjell
+ * @since 5.1
+ */
+public class ConfigInstanceSerializerTest {
+ @Test
+ public void test_that_leaf_types_are_serialized_to_json_types() throws IOException {
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ builder.boolval(false);
+ builder.stringval("foo");
+ builder.intval(13);
+ builder.longval(14);
+ builder.doubleval(3.14);
+ builder.enumval(SimpletypesConfig.Enumval.Enum.VAL2);
+
+ final SimpletypesConfig config = new SimpletypesConfig(builder);
+ final String expectedJson = inputJson(
+ "{",
+ " 'boolval': false,",
+ " 'doubleval': 3.14,",
+ " 'enumval': 'VAL2',",
+ " 'intval': 13,",
+ " 'longval': 14,",
+ " 'stringval': 'foo'",
+ "}"
+ );
+
+ assertConfigEquals(expectedJson, config);
+ }
+
+ @Test
+ public void test_that_nested_structs_are_formatted_to_json() throws IOException {
+ StructtypesConfig.Builder builder = new StructtypesConfig.Builder();
+ StructtypesConfig.Nested.Builder nestedBuilder = new StructtypesConfig.Nested.Builder();
+ StructtypesConfig.Nested.Inner.Builder innerBuilder = new StructtypesConfig.Nested.Inner.Builder();
+ innerBuilder.name("foo");
+ innerBuilder.emails("lulf@foo");
+ innerBuilder.emails("lulf@bar");
+ innerBuilder.gender(StructtypesConfig.Nested.Inner.Gender.Enum.MALE);
+ nestedBuilder.inner(innerBuilder);
+ builder.nested(nestedBuilder);
+ StructtypesConfig.Nestedarr.Builder nestedArrBuilder = new StructtypesConfig.Nestedarr.Builder();
+ StructtypesConfig.Nestedarr.Inner.Builder innerNestedArrBuilder = new StructtypesConfig.Nestedarr.Inner.Builder();
+ innerNestedArrBuilder.emails("foo@bar");
+ innerNestedArrBuilder.name("bar");
+ innerNestedArrBuilder.gender(StructtypesConfig.Nestedarr.Inner.Gender.Enum.FEMALE);
+ nestedArrBuilder.inner(innerNestedArrBuilder);
+ builder.nestedarr(nestedArrBuilder);
+
+ final StructtypesConfig config = new StructtypesConfig(builder);
+ final String expectedJson = inputJson(
+ "{",
+ " 'complexarr': [],",
+ " 'nested': {",
+ " 'inner': {",
+ " 'emails': [",
+ " 'lulf@foo',",
+ " 'lulf@bar'",
+ " ],",
+ " 'gender': 'MALE',",
+ " 'name': 'foo'",
+ " }",
+ " },",
+ " 'nestedarr': [",
+ " {",
+ " 'inner': {",
+ " 'emails': [",
+ " 'foo@bar'",
+ " ],",
+ " 'gender': 'FEMALE',",
+ " 'name': 'bar'",
+ " }",
+ " }",
+ " ],",
+ " 'simple': {",
+ " 'emails': [],",
+ " 'gender': 'MALE',",
+ " 'name': '_default_'",
+ " },",
+ " 'simplearr': []",
+ "}"
+ );
+
+ assertConfigEquals(expectedJson, config);
+ }
+
+ @Test
+ public void test_that_arrays_are_formatted_to_json() throws IOException {
+ ArraytypesConfig.Builder builder = new ArraytypesConfig.Builder();
+ builder.boolarr(true);
+ builder.boolarr(false);
+ builder.doublearr(1.2);
+ builder.doublearr(1.1);
+ builder.enumarr(ArraytypesConfig.Enumarr.Enum.VAL1);
+ builder.enumarr(ArraytypesConfig.Enumarr.Enum.VAL2);
+ builder.intarr(3);
+ builder.longarr(4l);
+ builder.stringarr("foo");
+
+ final ArraytypesConfig config = new ArraytypesConfig(builder);
+ final String expectedJson = inputJson(
+ "{",
+ " 'boolarr': [",
+ " true,",
+ " false",
+ " ],",
+ " 'doublearr': [",
+ " 1.2,",
+ " 1.1",
+ " ],",
+ " 'enumarr': [",
+ " 'VAL1',",
+ " 'VAL2'",
+ " ],",
+ " 'intarr': [",
+ " 3",
+ " ],",
+ " 'longarr': [",
+ " 4",
+ " ],",
+ " 'stringarr': [",
+ " 'foo'",
+ " ]",
+ "}"
+ );
+
+ assertConfigEquals(expectedJson, config);
+ }
+
+ @Test
+ public void test_that_maps_are_formatted_to_json() throws IOException {
+ MaptypesConfig.Builder builder = new MaptypesConfig.Builder();
+ builder.boolmap("foo", true);
+ builder.intmap("bar", 3);
+ builder.stringmap("hei", "hallo");
+ MaptypesConfig.Innermap.Builder inner = new MaptypesConfig.Innermap.Builder();
+ inner.foo(133);
+ builder.innermap("baz", inner);
+ MaptypesConfig.Nestedmap.Builder nested = new MaptypesConfig.Nestedmap.Builder();
+ nested.inner("foo", 33);
+ builder.nestedmap("bim", nested);
+
+ final MaptypesConfig config = new MaptypesConfig(builder);
+ final String expectedJson = inputJson(
+ "{",
+ " 'boolmap': {",
+ " 'foo': true",
+ " },",
+ " 'doublemap': {},",
+ " 'filemap': {},",
+ " 'innermap': {",
+ " 'baz': {",
+ " 'foo': 133",
+ " }",
+ " },",
+ " 'intmap': {",
+ " 'bar': 3",
+ " },",
+ " 'longmap': {},",
+ " 'nestedmap': {",
+ " 'bim': {",
+ " 'inner': {",
+ " 'foo': 33",
+ " }",
+ " }",
+ " },",
+ " 'stringmap': {",
+ " 'hei': 'hallo'",
+ " }",
+ "}"
+ );
+
+ assertConfigEquals(expectedJson, config);
+ }
+
+ @Test
+ public void test_that_non_standard_types_are_formatted_as_json_strings() throws IOException {
+ SpecialtypesConfig.Builder builder = new SpecialtypesConfig.Builder();
+ builder.myfile("thefilename");
+ builder.myref("thereference");
+
+ final SpecialtypesConfig config = new SpecialtypesConfig(builder);
+ final String expectedJson = inputJson(
+ "{",
+ " 'myfile': 'thefilename',",
+ " 'myref': 'thereference'",
+ "}"
+ );
+
+ assertConfigEquals(expectedJson, config);
+ }
+
+ static void assertConfigEquals(String expectedJson, ConfigInstance config) {
+ Slime slime = new Slime();
+ ConfigInstance.serialize(config, new ConfigInstanceSerializer(slime));
+ JsonFormat jsonFormat = new JsonFormat(true);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try {
+ jsonFormat.encode(baos, slime);
+ } catch (IOException e) {
+ fail();
+ }
+
+ assertJsonEquals(baos.toString(), expectedJson);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java
new file mode 100644
index 00000000000..ea7312ec7ef
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceTest.java
@@ -0,0 +1,119 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.AppConfig;
+import com.yahoo.foo.TestNonstringConfig;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests different aspects of the ConfigInstance class and its underlying Nodes.
+ *
+ * @author <a href="gv@yahoo-inc.com">G. Voldengen</a>
+ */
+public class ConfigInstanceTest {
+ private ConfigSourceSet sourceSet = new ConfigSourceSet("config-instance-test");
+
+ /**
+ * Verifies that the subscriber's configure() method is only
+ * called once upon subscribe, even if there are more than one
+ * subscribers to the same ConfigInstance. This has previously
+ * been a problem, since ConfigInstance.subscribe() called
+ * configureSubscriber(), which configures all subscribers to the
+ * instance. Now, the new method configureSubscriber(Subscriber)
+ * is called instead.
+ */
+ @Test
+ public void testConfigureOnlyOnceUponSubscribe() {
+ final String configId = "raw:times 1\n";
+ AppService service1 = new AppService(configId, sourceSet);
+ AppService service2 = new AppService(configId, sourceSet);
+
+ assertEquals(1, service1.timesConfigured());
+ assertEquals(1, service2.timesConfigured());
+ }
+
+ /**
+ * Verifies that values set in previous setConfig() calls are
+ * retained when the payload in a new setConfig() call does not
+ * overwrite them.
+ */
+ @Test
+ @Ignore
+ public void testRetainOldValuesOnConfigUpdates() {
+ AppConfig config = new AppConfig(new AppConfig.Builder());
+ //config.setConfig(Arrays.asList("message \"one\"", "times 333"), "", 0L);
+ assertEquals("one", config.message());
+ assertEquals(333, config.times());
+
+ //config.setConfig(Arrays.asList("message \"two\""), "", 0L);
+ assertEquals("two", config.message());
+ assertEquals("config.times retains previously set value", 333, config.times());
+
+ //config.setConfig(Arrays.asList("times 666"), "", 0L);
+ assertEquals("config.message retains previously set value", "two", config.message());
+ assertEquals(666, config.times());
+ }
+
+ /**
+ * Verifies that an exception is thrown when one attempts to set an
+ * illegal config value for parameters that have default values.
+ */
+ @Test
+ public void testFailUponIllegalValue() {
+ verifyIllegalValue("i notAnInt");
+ verifyIllegalValue("i 3.0");
+
+ verifyIllegalValue("d noDouble");
+ verifyIllegalValue("d 3.0.");
+
+ // verifyIllegalValue("b notTrueOrFalse");
+ //verifyIllegalValue("b 1");
+ //verifyIllegalValue("b 0");
+
+ verifyIllegalValue("e undeclaredEnumValue");
+ verifyIllegalValue("e 0");
+ }
+
+ private void verifyIllegalValue(String line) {
+ String configId = "raw:" + line + "\n";
+ try {
+ new TestNonstring(configId);
+ fail("Expected ConfigurationRuntimeException when setting a parameter value of wrong type.");
+ } catch (RuntimeException expected) {
+ verifyException(expected, "Not able to create config builder for payload", "Got ConfigurationRuntimeException for the wrong reason. " +
+ "Expected to fail when setting a parameter value of wrong type.");
+ }
+
+ }
+
+ private void verifyException(Throwable throwable, String expected, String failMessage) {
+ Throwable t = throwable;
+ boolean ok = false;
+ while (t != null) {
+ if (t.getMessage() != null && t.getMessage().contains(expected)) {
+ ok = true;
+ break;
+ }
+ t = t.getCause();
+ }
+ if (!ok) {
+ throwable.printStackTrace();
+ fail(failMessage);
+ }
+ }
+
+ private class TestNonstring {
+ private final ConfigSubscriber subscriber;
+ private final ConfigHandle<TestNonstringConfig> handle;
+ public TestNonstring(String configId) {
+ subscriber = new ConfigSubscriber();
+ handle = subscriber.subscribe(TestNonstringConfig.class, configId);
+ subscriber.nextConfig();
+ handle.getConfig();
+ }
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceUtilTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceUtilTest.java
new file mode 100644
index 00000000000..36ea09db1dc
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstanceUtilTest.java
@@ -0,0 +1,172 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigBuilder;
+import com.yahoo.config.ConfigurationRuntimeException;
+import com.yahoo.config.FileReference;
+import com.yahoo.foo.FunctionTestConfig;
+import com.yahoo.foo.TestNodefsConfig;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.ConfigPayloadBuilder;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static com.yahoo.foo.FunctionTestConfig.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigInstanceUtilTest {
+
+ @Test
+ public void require_that_builder_values_can_be_overridden_by_another_builder() {
+ FunctionTestConfig.Builder destination = createVariableAccessBuilder();
+
+ FunctionTestConfig.Builder source = new FunctionTestConfig.Builder()
+ .int_val(-1)
+ .intarr(0)
+ .doublearr(0.0)
+ .basicStruct(new FunctionTestConfig.BasicStruct.Builder()
+ .bar(-1)
+ .intArr(0))
+ .myarray(new FunctionTestConfig.Myarray.Builder()
+ .intval(-1)
+ .refval("")
+ .fileVal("")
+ .myStruct(new FunctionTestConfig.Myarray.MyStruct.Builder()
+ .a(0)
+ ));
+
+ ConfigInstanceUtil.setValues(destination, source);
+
+ FunctionTestConfig result = new FunctionTestConfig(destination);
+ assertThat(result.int_val(), is(-1));
+ assertThat(result.string_val(), is("foo"));
+ assertThat(result.intarr().size(), is(1));
+ assertThat(result.intarr(0), is(0));
+ assertThat(result.longarr().size(), is(2));
+ assertThat(result.doublearr().size(), is(3));
+ assertEquals(2344.0, result.doublearr(0), 0.01);
+ assertEquals(123.0, result.doublearr(1), 0.01);
+ assertEquals(0.0, result.doublearr(2), 0.01);
+ assertThat(result.basicStruct().bar(), is(-1));
+ assertThat(result.basicStruct().foo(), is("basicFoo"));
+ assertThat(result.basicStruct().intArr().size(), is(3));
+ assertThat(result.basicStruct().intArr(0), is(310));
+ assertThat(result.basicStruct().intArr(1), is(311));
+ assertThat(result.basicStruct().intArr(2), is(0));
+ assertThat(result.myarray().size(), is(3));
+ assertThat(result.myarray(2).intval(), is(-1));
+ assertThat(result.myarray(2).refval(), is(""));
+ assertThat(result.myarray(2).fileVal().value(), is(""));
+ assertThat(result.myarray(2).myStruct().a(), is(0));
+
+ }
+
+ @Test(expected = ConfigurationRuntimeException.class)
+ public void require_that_invalid_builders_fail() {
+ ConfigInstanceUtil.setValues(new FakeBuilder(), new FakeBuilder());
+ }
+
+ private static class FakeBuilder implements ConfigBuilder {
+ }
+
+ static FunctionTestConfig.Builder createVariableAccessBuilder() {
+ return new FunctionTestConfig.Builder().
+ bool_val(false).
+ bool_with_def(true).
+ int_val(5).
+ int_with_def(-14).
+ long_val(12345678901L).
+ long_with_def(-9876543210L).
+ double_val(41.23).
+ double_with_def(-12).
+ string_val("foo").
+ stringwithdef("bar and foo").
+ enum_val(Enum_val.FOOBAR).
+ enumwithdef(Enumwithdef.BAR2).
+ refval(":parent:").
+ refwithdef(":parent:").
+ fileVal("etc").
+ pathVal(FileReference.mockFileReferenceForUnitTesting(new File("src/test/resources/configs/def-files/function-test.def"))).
+ boolarr(false).
+ longarr(9223372036854775807L).
+ longarr(-9223372036854775808L).
+ doublearr(2344.0).
+ doublearr(123.0).
+ stringarr("bar").
+ enumarr(Enumarr.VALUES).
+ refarr(Arrays.asList(":parent:", ":parent", "parent:")). // test collection based setter
+ fileArr("bin").
+
+ basicStruct(new FunctionTestConfig.BasicStruct.Builder().
+ foo("basicFoo").
+ bar(3).
+ intArr(310).intArr(311)).
+
+ rootStruct(new FunctionTestConfig.RootStruct.Builder().
+ inner0(new FunctionTestConfig.RootStruct.Inner0.Builder().
+ index(11)).
+ inner1(new FunctionTestConfig.RootStruct.Inner1.Builder().
+ index(12)).
+ innerArr(new FunctionTestConfig.RootStruct.InnerArr.Builder().
+ boolVal(true).
+ stringVal("deep")).
+ innerArr(new FunctionTestConfig.RootStruct.InnerArr.Builder().
+ boolVal(false).
+ stringVal("blue a=\"escaped\""))).
+
+ myarray(new FunctionTestConfig.Myarray.Builder().
+ intval(-5).
+ stringval("baah").
+ stringval("yikes").
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file0").
+ anotherarray(new FunctionTestConfig.Myarray.Anotherarray.Builder().
+ foo(7)).
+ myStruct(new FunctionTestConfig.Myarray.MyStruct.Builder().
+ a(1).
+ b(2))).
+
+ myarray(new FunctionTestConfig.Myarray.Builder().
+ intval(5).
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file1").
+ anotherarray(new FunctionTestConfig.Myarray.Anotherarray.Builder().
+ foo(1).
+ foo(2)).
+ myStruct(new FunctionTestConfig.Myarray.MyStruct.Builder().
+ a(-1).
+ b(-2)));
+
+ }
+
+ @Test
+ public void test_require_private_getter() {
+ FunctionTestConfig.Builder builder = createVariableAccessBuilder();
+ assertEquals(ConfigInstanceUtil.getField(builder, "int_val"), 5);
+ FunctionTestConfig conf = new FunctionTestConfig(builder);
+ assertEquals(conf.int_val(), 5);
+ }
+
+ @Test
+ public void testGetNewInstanceErrorMessage() {
+ ConfigPayloadBuilder payloadBuilder = new ConfigPayloadBuilder();
+ try {
+ ConfigInstanceUtil.getNewInstance(TestNodefsConfig.class, "id0", ConfigPayload.fromBuilder(payloadBuilder));
+ assert(false);
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("Failed creating new instance of 'com.yahoo.foo.TestNodefsConfig' for config id 'id0':"));
+ }
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInterruptedExceptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInterruptedExceptionTest.java
new file mode 100644
index 00000000000..d760a1c1b72
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInterruptedExceptionTest.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigInterruptedExceptionTest {
+ @Test
+ public void require_that_throwable_is_preserved() {
+ ConfigInterruptedException e = new ConfigInterruptedException(new RuntimeException("foo"));
+ assertThat(e.getCause().getMessage(), is("foo"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java
new file mode 100644
index 00000000000..adbc11b7ca0
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSetSubscriptionTest.java
@@ -0,0 +1,133 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import static org.junit.Assert.*;
+
+import com.yahoo.foo.AppConfig;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.StringConfig;
+import com.yahoo.config.subscription.impl.ConfigSubscription;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.TimingValues;
+import org.junit.Test;
+
+public class ConfigSetSubscriptionTest {
+
+ @Test
+ public void testConfigSubscription() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigSet configSet = new ConfigSet();
+ AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 0").times(88);
+ configSet.addBuilder("app/0", a0builder);
+ AppConfig.Builder a1builder = new AppConfig.Builder().message("A message, 1").times(89);
+ configSet.addBuilder("app/1", a1builder);
+
+ ConfigSubscription<AppConfig> c1 = ConfigSubscription.get(
+ new ConfigKey<>(AppConfig.class, "app/0"),
+ subscriber,
+ configSet,
+ new TimingValues());
+ ConfigSubscription<AppConfig> c2 = ConfigSubscription.get(
+ new ConfigKey<>(AppConfig.class, "app/1"),
+ subscriber,
+ configSet,
+ new TimingValues());
+
+ assertTrue(c1.equals(c1));
+ assertFalse(c1.equals(c2));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownKey() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigSet configSet = new ConfigSet();
+ AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 0").times(88);
+ configSet.addBuilder("app/0", a0builder);
+
+ ConfigSubscription.get(
+ new ConfigKey<>(SimpletypesConfig.class, "simpletypes/1"),
+ subscriber,
+ configSet,
+ new TimingValues());
+ }
+
+ @Test
+ public void testConfigSetBasic() {
+ ConfigSet myConfigs = new ConfigSet();
+ AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 0").times(88);
+ AppConfig.Builder a1builder = new AppConfig.Builder().message("A message, 1").times(89);
+ StringConfig.Builder barBuilder = new StringConfig.Builder().stringVal("StringVal");
+ myConfigs.addBuilder("app/0", a0builder);
+ myConfigs.addBuilder("app/1", a1builder);
+ myConfigs.addBuilder("bar", barBuilder);
+ ConfigSubscriber subscriber = new ConfigSubscriber(myConfigs);
+ ConfigHandle<AppConfig> hA0 = subscriber.subscribe(AppConfig.class, "app/0");
+ ConfigHandle<AppConfig> hA1 = subscriber.subscribe(AppConfig.class, "app/1");
+ ConfigHandle<StringConfig> hS = subscriber.subscribe(StringConfig.class, "bar");
+
+ assertTrue(subscriber.nextConfig(0));
+ assertTrue(hA0.isChanged());
+ assertTrue(hA1.isChanged());
+ assertTrue(hS.isChanged());
+
+ assertEquals(hA0.getConfig().message(), "A message, 0");
+ assertEquals(hA1.getConfig().message(), "A message, 1");
+ assertEquals(hA0.getConfig().times(), 88);
+ assertEquals(hA1.getConfig().times(), 89);
+
+ assertFalse(subscriber.nextConfig(10));
+ assertFalse(hA0.isChanged());
+ assertFalse(hA1.isChanged());
+ assertFalse(hS.isChanged());
+ assertEquals(hA0.getConfig().message(), "A message, 0");
+ assertEquals(hA1.getConfig().message(), "A message, 1");
+ assertEquals(hA0.getConfig().times(), 88);
+ assertEquals(hA1.getConfig().times(), 89);
+ assertEquals(hS.getConfig().stringVal(), "StringVal");
+
+ //Reconfigure all configs, generation should change
+ a0builder.message("A new message, 0").times(880);
+ a1builder.message("A new message, 1").times(890);
+ barBuilder.stringVal("new StringVal");
+ subscriber.reload(1);
+ assertTrue(subscriber.nextConfig(0));
+ assertTrue(hA0.isChanged());
+ assertTrue(hA1.isChanged());
+ assertTrue(hS.isChanged());
+
+ assertEquals(hA0.getConfig().message(), "A new message, 0");
+ assertEquals(hA1.getConfig().message(), "A new message, 1");
+ assertEquals(hA0.getConfig().times(), 880);
+ assertEquals(hA1.getConfig().times(), 890);
+ assertEquals(hS.getConfig().stringVal(), "new StringVal");
+
+ // Reconfigure only one
+ a0builder.message("Another new message, 0").times(8800);
+ subscriber.reload(2);
+ assertTrue(subscriber.nextConfig(0));
+ assertTrue(hA0.isChanged());
+ assertFalse(hA1.isChanged());
+ assertFalse(hS.isChanged());
+
+ assertEquals(hA0.getConfig().message(), "Another new message, 0");
+ assertEquals(hA1.getConfig().message(), "A new message, 1");
+ assertEquals(hA0.getConfig().times(), 8800);
+ assertEquals(hA1.getConfig().times(), 890);
+ assertEquals(hS.getConfig().stringVal(), "new StringVal");
+
+ //Reconfigure only one, and only one field on the builder
+ a1builder.message("Yet another message, 1");
+ subscriber.reload(3);
+ assertTrue(subscriber.nextConfig(0));
+ assertFalse(hA0.isChanged());
+ assertTrue(hA1.isChanged());
+ assertFalse(hS.isChanged());
+
+ assertEquals(hA0.getConfig().message(), "Another new message, 0");
+ assertEquals(hA1.getConfig().message(), "Yet another message, 1");
+ assertEquals(hA0.getConfig().times(), 8800);
+ assertEquals(hA1.getConfig().times(), 890);
+ assertEquals(hS.getConfig().stringVal(), "new StringVal");
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSetTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSetTest.java
new file mode 100644
index 00000000000..668fa68d264
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSetTest.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.SimpletypesConfig;
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigSetTest {
+ @Test
+ public void testToString() {
+ ConfigSet set = new ConfigSet();
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ set.addBuilder("foo", builder);
+ assertTrue(Pattern.matches("name=simpletypes,namespace=foo,configId=foo=>com.yahoo.foo.SimpletypesConfig.*",
+ set.toString()));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java
new file mode 100755
index 00000000000..23633a62cca
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceSetTest.java
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import org.junit.Test;
+import org.junit.After;
+
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="gv@yahoo-inc.com">G. Voldengen</a>
+ */
+public class ConfigSourceSetTest {
+ @Test
+ public void testEquals() {
+ assertTrue(new ConfigSourceSet().equals(new ConfigSourceSet()));
+ assertFalse(new ConfigSourceSet().equals(new ConfigSourceSet(new String[]{"a"})));
+
+ assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"a"})));
+ assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{" A "})));
+ assertTrue(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"A", "a"})));
+ assertTrue(new ConfigSourceSet(new String[]{"A"}).equals(new ConfigSourceSet(new String[]{"a", " a "})));
+
+ assertFalse(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"b"})));
+ assertFalse(new ConfigSourceSet(new String[]{"a"}).equals(new ConfigSourceSet(new String[]{"a", "b"})));
+
+ assertTrue(new ConfigSourceSet(new String[]{"a", "b"}).equals(new ConfigSourceSet(new String[]{"a", "b"})));
+ assertTrue(new ConfigSourceSet(new String[]{"b", "a"}).equals(new ConfigSourceSet(new String[]{"a", "b"})));
+ assertTrue(new ConfigSourceSet(new String[]{"A", " b"}).equals(new ConfigSourceSet(new String[]{"a ", "B"})));
+ assertTrue(new ConfigSourceSet(new String[]{"b", "a", "c"})
+ .equals(new ConfigSourceSet(new String[]{"a", "b", "c"})));
+
+ assertFalse(new ConfigSourceSet(new String[]{"a", "b"}).equals(new ConfigSourceSet(new String[]{"b", "c"})));
+ assertFalse(new ConfigSourceSet().equals("foo"));
+ }
+
+ @Test
+ public void testIterationOrder() {
+ String[] hosts = new String[]{"primary", "fallback", "last-resort"};
+ ConfigSourceSet css = new ConfigSourceSet(hosts);
+
+ Set<String> sources = css.getSources();
+ assertEquals(hosts.length, sources.size());
+ int i = 0;
+ for (String s : sources) {
+ assertEquals(hosts[i++], s);
+ }
+ }
+
+ @Test
+ public void testDefaultSourceFromProperty() {
+ // TODO: Unable to set environment, so only able to test property usage for now
+ System.setProperty("configsources", "foo:123,bar:345,tcp/baz:333,quux");
+ ConfigSourceSet set = ConfigSourceSet.createDefault();
+ assertThat(set.getSources().size(), is(4));
+ assertTrue(set.getSources().contains("tcp/foo:123"));
+ assertTrue(set.getSources().contains("tcp/bar:345"));
+ assertTrue(set.getSources().contains("tcp/baz:333"));
+ assertTrue(set.getSources().contains("tcp/quux"));
+ }
+
+ @After
+ public void cleanup() {
+ System.clearProperty("configsources");
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSourceTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceTest.java
new file mode 100644
index 00000000000..d928592ebe9
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSourceTest.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.SimpletypesConfig;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigSourceTest {
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_FileSource_throws_exception_on_invalid_file() {
+ new FileSource(new File("invalid"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_DirSource_throws_exception_on_invalid_dir() {
+ new DirSource(new File("invalid"));
+ }
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+
+ @Test
+ public void require_that_DirSource_throws_exception_on_missing_file() throws IOException {
+ File folder = tmpDir.newFolder();
+ DirSource dirSource = new DirSource(folder);
+ try {
+ ConfigGetter.getConfig(SimpletypesConfig.class, "dir:" + tmpDir, dirSource);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), is("Could not find a config file for '" + SimpletypesConfig.getDefName() + "' in '" + folder + "/'"));
+ }
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java
new file mode 100644
index 00000000000..9fa299aef83
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigSubscriptionTest.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.ConfigurationRuntimeException;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.AppConfig;
+import com.yahoo.config.subscription.impl.ConfigSubscription;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.TimingValues;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author musum, lulf
+ * @since 5.1
+ */
+public class ConfigSubscriptionTest {
+ @Test
+ public void testEquals() {
+ ConfigSubscriber sub = new ConfigSubscriber();
+ final String payload = "boolval true";
+ ConfigSubscription<SimpletypesConfig> a = ConfigSubscription.get(new ConfigKey<>(SimpletypesConfig.class, "test"),
+ sub, new RawSource(payload), new TimingValues());
+ ConfigSubscription<SimpletypesConfig> b = ConfigSubscription.get(new ConfigKey<>(SimpletypesConfig.class, "test"),
+ sub, new RawSource(payload), new TimingValues());
+ ConfigSubscription<SimpletypesConfig> c = ConfigSubscription.get(new ConfigKey<>(SimpletypesConfig.class, "test2"),
+ sub, new RawSource(payload), new TimingValues());
+ assertThat(a, is(b));
+ assertThat(a, is(a));
+ assertThat(b, is(b));
+ assertThat(c, is(c));
+ assertThat(a, is(not(c)));
+ assertThat(b, is(not(c)));
+
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigSet configSet = new ConfigSet();
+ AppConfig.Builder a0builder = new AppConfig.Builder().message("A message, 0").times(88);
+ configSet.addBuilder("app/0", a0builder);
+ AppConfig.Builder a1builder = new AppConfig.Builder().message("A message, 1").times(89);
+ configSet.addBuilder("app/1", a1builder);
+
+ ConfigSubscription<AppConfig> c1 = ConfigSubscription.get(
+ new ConfigKey<>(AppConfig.class, "app/0"),
+ subscriber,
+ configSet,
+ new TimingValues());
+ ConfigSubscription<AppConfig> c2 = ConfigSubscription.get(
+ new ConfigKey<>(AppConfig.class, "app/1"),
+ subscriber,
+ configSet,
+ new TimingValues());
+
+ assertTrue(c1.equals(c1));
+ assertFalse(c1.equals(c2));
+ }
+
+ @Test
+ public void testSubscribeInterface() {
+ ConfigSubscriber sub = new ConfigSubscriber();
+ ConfigHandle<SimpletypesConfig> handle = sub.subscribe(SimpletypesConfig.class, "raw:boolval true", 10000);
+ assertNotNull(handle);
+ sub.nextConfig();
+ assertThat(handle.getConfig().boolval(), is(true));
+ //assertTrue(sub.getSource() instanceof RawSource);
+ }
+
+ // Test that subscription is closed and subscriptionHandles is empty if we get an exception (only the last is possible to
+ // test right now).
+ @Test
+ @Ignore
+ public void testSubscribeWithException() {
+ TestConfigSubscriber sub = new TestConfigSubscriber();
+ ConfigSourceSet configSourceSet = new ConfigSourceSet(Collections.singletonList("tcp/localhost:99999"));
+ try {
+ sub.subscribe(SimpletypesConfig.class, "configid", configSourceSet, new TimingValues().setSubscribeTimeout(100));
+ fail();
+ } catch (ConfigurationRuntimeException e) {
+ assertThat(sub.getSubscriptionHandles().size(), is(0));
+ }
+ }
+
+ private static class TestConfigSubscriber extends ConfigSubscriber {
+ List<ConfigHandle<? extends ConfigInstance>> getSubscriptionHandles() {
+ return subscriptionHandles;
+ }
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigURITest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigURITest.java
new file mode 100644
index 00000000000..15cfff0448a
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/ConfigURITest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigURITest {
+ @Test
+ public void testDefaultUri() {
+ ConfigURI uri = ConfigURI.createFromId("foo");
+ assertThat(uri.getConfigId(), is("foo"));
+ assertTrue(uri.getSource() instanceof ConfigSourceSet);
+ }
+
+ @Test
+ public void testFileUri() throws IOException {
+ File file = File.createTempFile("foo", ".cfg");
+ ConfigURI uri = ConfigURI.createFromId("file:" + file.getAbsolutePath());
+ assertThat(uri.getConfigId(), is(""));
+ assertTrue(uri.getSource() instanceof FileSource);
+ }
+
+ @Test
+ public void testDirUri() throws IOException {
+ ConfigURI uri = ConfigURI.createFromId("dir:.");
+ assertThat(uri.getConfigId(), is(""));
+ assertTrue(uri.getSource() instanceof DirSource);
+ }
+
+ @Test
+ public void testCustomUri() {
+ ConfigURI uri = ConfigURI.createFromIdAndSource("foo", new ConfigSet());
+ assertThat(uri.getConfigId(), is("foo"));
+ assertTrue(uri.getSource() instanceof ConfigSet);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/DefaultConfigTest.java b/config/src/test/java/com/yahoo/config/subscription/DefaultConfigTest.java
new file mode 100644
index 00000000000..55b1eb97bee
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/DefaultConfigTest.java
@@ -0,0 +1,66 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.*;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests reading of a config containing
+ * <ul>
+ * <li>Missing values
+ * <li>Default values
+ * </ul>
+ * <p/>
+ * for
+ * <p/>
+ * <ul>
+ * <li>String and
+ * <li>Reference
+ * </ul>
+ *
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class DefaultConfigTest {
+ static final String CONFIG_ID = "raw:" +
+ "nondefaultstring ####-------missing--------\n" +
+ "defaultstring \"thedefault\"\n" +
+ "nondefaultreference ####-------missing--------\n" +
+ "defaultreference \"thedefault\"\n";
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFailUponUnitializedValue() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ subscriber.subscribe(DefaulttestConfig.class, "raw:" +
+ "defaultstring \"new value\"");
+ subscriber.nextConfig();
+ subscriber.close();
+ }
+
+ /**
+ * Reads a config from a string which is exactly like one returned from
+ * the config server given only default values for this config.
+ * The parsing code is the same whether the reading happens from string
+ * or from a server connection, so this tests that this config can be
+ * received correctly from the server
+ */
+ @Test
+ public void testDefaultConfig() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigHandle<DefaulttestConfig> h = subscriber.subscribe(DefaulttestConfig.class, CONFIG_ID);
+ assertTrue(subscriber.nextConfig());
+ DefaulttestConfig config = h.getConfig();
+ verifyConfigValues(config);
+ subscriber.close();
+ }
+
+ private static void verifyConfigValues(DefaulttestConfig config) {
+ assertEquals("####-------missing--------", config.nondefaultstring());
+ assertEquals("thedefault", config.defaultstring());
+ assertEquals("####-------missing--------", config.nondefaultreference());
+ assertEquals("thedefault", config.defaultreference());
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java
new file mode 100644
index 00000000000..8936c62784b
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java
@@ -0,0 +1,259 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.FunctionTestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * Test most data types and features of the config client API,
+ * e.g. parameter access, missing default values, and more.
+ * <p/>
+ * NOTE: this test does NOT test null default values.
+ *
+ * @author gjoranv
+ */
+public class FunctionTest {
+
+ public static final String PATH = "src/test/resources/configs/function-test/";
+
+ private FunctionTestConfig config;
+ private ConfigSourceSet sourceSet = new ConfigSourceSet("function-test");
+
+ public void configure(FunctionTestConfig config, ConfigSourceSet sourceSet) {
+ this.config = config;
+ assertEquals(this.sourceSet, sourceSet);
+ }
+
+ @Before
+ public void resetConfig() {
+ config = null;
+ }
+
+ @Test
+ public void testVariableAccess() {
+ assertNull(config);
+ String configId = "file:" + PATH + "variableaccess.txt";
+ ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<>(FunctionTestConfig.class);
+ assertVariableAccessValues(getter.getConfig(configId), configId);
+ }
+
+ @Test
+ public void testDefaultValues() {
+ assertNull(config);
+ String configId = "file:" + PATH + "defaultvalues.txt";
+ ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<>(FunctionTestConfig.class);
+ FunctionTestConfig config = getter.getConfig(configId);
+
+ assertFalse(config.bool_val());
+ assertFalse(config.bool_with_def());
+ assertEquals(5, config.int_val());
+ assertEquals(-545, config.int_with_def());
+ assertEquals(1234567890123L, config.long_val());
+ assertEquals(-50000000000L, config.long_with_def());
+ assertEquals(41.23, config.double_val(), 0.000001);
+ assertEquals(-6.43, config.double_with_def(), 0.000001);
+ assertEquals("foo", config.string_val());
+ assertEquals("foobar", config.stringwithdef());
+ assertEquals(FunctionTestConfig.Enum_val.FOOBAR, config.enum_val());
+ assertEquals(FunctionTestConfig.Enumwithdef.BAR2, config.enumwithdef());
+ assertEquals(configId, config.refval());
+ assertEquals(configId, config.refwithdef());
+ assertEquals("vespa.log", config.fileVal().value());
+ assertEquals(1, config.boolarr().size());
+ assertEquals(0, config.intarr().size());
+ assertEquals(0, config.longarr().size());
+ assertEquals(2, config.doublearr().size());
+ assertEquals(1, config.stringarr().size());
+ assertEquals(1, config.enumarr().size());
+ assertEquals(0, config.refarr().size());
+ assertEquals(0, config.fileArr().size());
+
+ assertEquals(3, config.basicStruct().bar());
+ assertEquals(1, config.basicStruct().intArr().size());
+ assertEquals(10, config.basicStruct().intArr(0));
+ assertEquals(11, config.rootStruct().inner0().index());
+ assertEquals(12, config.rootStruct().inner1().index());
+
+ assertEquals(2, config.myarray().size());
+ assertEquals(1, config.myarray(0).myStruct().a());
+ assertEquals(-1, config.myarray(1).myStruct().a());
+ assertEquals("command.com", config.myarray(0).fileVal().value());
+ assertEquals("display.sys", config.myarray(1).fileVal().value());
+ }
+
+
+ @Test
+ public void testRandomOrder() {
+ assertNull(config);
+ String configId = "file:" + PATH + "randomorder.txt";
+ ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<>(FunctionTestConfig.class);
+ FunctionTestConfig config = getter.getConfig(configId);
+ assertFalse(config.bool_val());
+ assertTrue(config.bool_with_def());
+ assertEquals(5, config.int_val());
+ assertEquals(-14, config.int_with_def());
+ assertEquals(666000666000L, config.long_val());
+ assertEquals(-333000333000L, config.long_with_def());
+ assertEquals(41.23, config.double_val(), 0.000001);
+ assertEquals(-12, config.double_with_def(), 0.000001);
+ assertEquals("foo", config.string_val());
+ assertEquals("bar and foo", config.stringwithdef());
+ assertEquals(FunctionTestConfig.Enum_val.FOOBAR, config.enum_val());
+ assertEquals(FunctionTestConfig.Enumwithdef.BAR2, config.enumwithdef());
+ assertEquals(configId, config.refval());
+ assertEquals(configId, config.refwithdef());
+ assertEquals("autoexec.bat", config.fileVal().value());
+ assertEquals(1, config.boolarr().size());
+ assertEquals(0, config.intarr().size());
+ assertEquals(0, config.longarr().size());
+ assertEquals(2, config.doublearr().size());
+ assertEquals(1, config.stringarr().size());
+ assertEquals(1, config.enumarr().size());
+ assertEquals(0, config.refarr().size());
+ assertEquals(0, config.fileArr().size());
+ assertEquals(2, config.myarray().size());
+ }
+
+ @Test
+ public void testLackingDefaults() throws IOException {
+ attemptLacking("bool_val", false);
+ attemptLacking("int_val", false);
+ attemptLacking("long_val", false);
+ attemptLacking("double_val", false);
+ attemptLacking("string_val", false);
+ attemptLacking("enum_val", false);
+ attemptLacking("refval", false);
+ attemptLacking("fileVal", false);
+
+ attemptLacking("boolarr", true);
+ attemptLacking("intarr", true);
+ attemptLacking("longarr", true);
+ attemptLacking("doublearr", true);
+ attemptLacking("enumarr", true);
+ attemptLacking("stringarr", true);
+ attemptLacking("refarr", true);
+ attemptLacking("fileArr", true);
+ attemptLacking("myarray", true);
+
+ attemptLacking("basicStruct.bar", false);
+ attemptLacking("rootStruct.inner0.index", false);
+ attemptLacking("rootStruct.inner1.index", false);
+// attemptLacking("rootStruct.innerArr[0].stringVal", false);
+
+ attemptLacking("myarray[0].stringval", true);
+ attemptLacking("myarray[0].refval", false);
+ attemptLacking("myarray[0].anotherarray", true);
+ attemptLacking("myarray[0].anotherarray", true);
+ attemptLacking("myarray[0].myStruct.a", false);
+ }
+
+ private void attemptLacking(String param, boolean isArray) throws IOException {
+ BufferedReader in = new BufferedReader(new FileReader(new File(PATH + "defaultvalues.txt")));
+ StringBuilder config = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) {
+ if ((line.length() > param.length()) &&
+ param.equals(line.substring(0, param.length())) &&
+ (line.charAt(param.length()) == ' ' || line.charAt(param.length()) == '[')) {
+ // Ignore values matched
+ } else {
+ config.append(line).append("\n");
+ }
+ }
+ //System.out.println("Config lacking " + param + "-> " + config + "\n");
+ try {
+ ConfigGetter<FunctionTestConfig> getter = new ConfigGetter<FunctionTestConfig>(FunctionTestConfig.class);
+ getter.getConfig("raw:\n" + config);
+ if (isArray) {
+ // Arrays are empty by default
+ return;
+ }
+ fail("Expected to fail when not specifying value " + param + " without default");
+ } catch (IllegalArgumentException expected) {
+ if (isArray) {
+ fail("Arrays should be empty by default.");
+ }
+ }
+ }
+
+ public static void assertVariableAccessValues(FunctionTestConfig config, String configId) {
+ assertTrue(config.bool_with_def());
+ assertEquals(5, config.int_val());
+ assertEquals(-14, config.int_with_def());
+ assertEquals(12345678901L, config.long_val());
+ assertEquals(-9876543210L, config.long_with_def());
+ assertEquals(41.23, config.double_val(), 0.000001);
+ assertEquals(-12, config.double_with_def(), 0.000001);
+ assertEquals("foo", config.string_val());
+ assertEquals("bar and foo", config.stringwithdef());
+ assertEquals(FunctionTestConfig.Enum_val.FOOBAR, config.enum_val());
+ assertEquals(FunctionTestConfig.Enumwithdef.BAR2, config.enumwithdef());
+ assertEquals(configId, config.refval());
+ assertEquals(configId, config.refwithdef());
+ assertEquals("etc", config.fileVal().value());
+ assertEquals(1, config.boolarr().size());
+ assertEquals(1, config.boolarr().size()); // new api with accessor for a List of the original Java type
+ assertEquals(false, config.boolarr().get(0)); // new List api
+ assertEquals(false, config.boolarr(0)); // short-hand
+ assertEquals(0, config.intarr().size());
+ assertEquals(2, config.longarr().size());
+ assertEquals(Long.MAX_VALUE, config.longarr(0));
+ assertThat(config.longarr().get(1), is(Long.MIN_VALUE));
+ assertEquals(2, config.doublearr().size());
+ assertEquals(1, config.stringarr().size());
+ assertEquals(1, config.enumarr().size());
+ assertEquals(FunctionTestConfig.Enumarr.VALUES, config.enumarr().get(0)); // new List api, don't have to call value()
+ assertEquals(3, config.refarr().size());
+ assertEquals(1, config.fileArr().size());
+ assertEquals(configId, config.refarr(0));
+ assertEquals(":parent", config.refarr(1));
+ assertEquals("parent:", config.refarr(2));
+ assertEquals("bin", config.fileArr(0).value());
+ assertEquals("pom.xml", config.pathArr(0).toString());
+ assertEquals("pom.xml", config.pathMap("one").toString());
+
+ assertEquals("basicFoo", config.basicStruct().foo());
+ assertEquals(3, config.basicStruct().bar()); // new List api
+ assertEquals(2, config.basicStruct().intArr().size());
+ assertThat(config.basicStruct().intArr().get(0), is(310)); // new List api
+ assertThat(config.basicStruct().intArr().get(1), is(311)); // new List api
+ assertEquals(310, config.basicStruct().intArr(0)); // short-hand
+ assertEquals("inner0", config.rootStruct().inner0().name()); // new List api
+ assertEquals(11, config.rootStruct().inner0().index());
+ assertEquals("inner1", config.rootStruct().inner1().name());
+ assertEquals(12, config.rootStruct().inner1().index());
+ assertEquals(2, config.rootStruct().innerArr().size());
+ assertEquals(true, config.rootStruct().innerArr(0).boolVal());
+ assertEquals("deep", config.rootStruct().innerArr(0).stringVal());
+ assertEquals(false, config.rootStruct().innerArr(1).boolVal());
+ assertEquals("blue a=\"escaped\"", config.rootStruct().innerArr(1).stringVal());
+
+ assertEquals(2, config.myarray().size()); // new List api
+ assertEquals(configId, config.myarray().get(0).refval()); // new List api
+ assertEquals(configId, config.myarray(0).refval()); // short-hand
+ assertEquals("file0", config.myarray(0).fileVal().value());
+ assertEquals(1, config.myarray(0).myStruct().a());
+ assertEquals(2, config.myarray(0).myStruct().b());
+ assertEquals(configId, config.myarray(1).refval());
+ assertEquals("file1", config.myarray(1).fileVal().value());
+ assertEquals(-1, config.myarray(1).myStruct().a());
+ assertEquals(-2, config.myarray(1).myStruct().b());
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/GenericConfigSubscriberTest.java b/config/src/test/java/com/yahoo/config/subscription/GenericConfigSubscriberTest.java
new file mode 100644
index 00000000000..26b47f7621c
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/GenericConfigSubscriberTest.java
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import java.util.*;
+
+import com.yahoo.config.subscription.impl.GenericConfigHandle;
+import com.yahoo.config.subscription.impl.GenericConfigSubscriber;
+import com.yahoo.config.subscription.impl.JRTConfigRequester;
+import com.yahoo.config.subscription.impl.JRTConfigRequesterTest;
+import com.yahoo.config.subscription.impl.MockConnection;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.JRTConnectionPool;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * Test cases for the "generic" (class-less) subscription mechanism.
+ *
+ * @author lulf
+ * @since 5.1
+ */
+public class GenericConfigSubscriberTest {
+
+ @Test
+ public void testSubscribeGeneric() {
+ Map<ConfigSourceSet, JRTConfigRequester> requesters = new HashMap<>();
+ ConfigSourceSet sourceSet = new ConfigSourceSet("blabla");
+ requesters.put(sourceSet, JRTConfigRequester.get(new MockConnection(), JRTConfigRequesterTest.getTestTimingValues()));
+ GenericConfigSubscriber sub = new GenericConfigSubscriber(requesters);
+ final List<String> defContent = Arrays.asList("myVal int");
+ GenericConfigHandle handle = sub.subscribe(new ConfigKey<>("simpletypes", "id", "config"), defContent, sourceSet, JRTConfigRequesterTest.getTestTimingValues());
+ assertTrue(sub.nextConfig());
+ assertTrue(handle.isChanged());
+ assertThat(handle.getRawConfig().getPayload().withCompression(CompressionType.UNCOMPRESSED).toString(), is("{}")); // MockConnection returns empty string
+ assertFalse(sub.nextConfig());
+ assertFalse(handle.isChanged());
+ }
+
+ @Test
+ public void testGenericRequesterPooling() {
+ ConfigSourceSet source1 = new ConfigSourceSet("tcp/foo:78");
+ ConfigSourceSet source2 = new ConfigSourceSet("tcp/bar:79");
+ JRTConfigRequester req1 = JRTConfigRequester.get(new JRTConnectionPool(source1), JRTConfigRequesterTest.getTestTimingValues());
+ JRTConfigRequester req2 = JRTConfigRequester.get(new JRTConnectionPool(source2), JRTConfigRequesterTest.getTestTimingValues());
+ Map<ConfigSourceSet, JRTConfigRequester> requesters = new LinkedHashMap<>();
+ requesters.put(source1, req1);
+ requesters.put(source2, req2);
+ GenericConfigSubscriber sub = new GenericConfigSubscriber(requesters);
+ assertEquals(sub.requesters().get(source1).getConnectionPool().getCurrent().getAddress(), "tcp/foo:78");
+ assertEquals(sub.requesters().get(source2).getConnectionPool().getCurrent().getAddress(), "tcp/bar:79");
+
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testOverriddenSubscribeInvalid1() {
+ GenericConfigSubscriber sub = new GenericConfigSubscriber();
+ sub.subscribe(null, null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testOverriddenSubscribeInvalid2() {
+ GenericConfigSubscriber sub = new GenericConfigSubscriber();
+ sub.subscribe(null, null, 0l);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testOverriddenSubscribeInvalid3() {
+ GenericConfigSubscriber sub = new GenericConfigSubscriber();
+ sub.subscribe(null, null, "");
+ }
+} \ No newline at end of file
diff --git a/config/src/test/java/com/yahoo/config/subscription/NamespaceTest.java b/config/src/test/java/com/yahoo/config/subscription/NamespaceTest.java
new file mode 100644
index 00000000000..90be475bb94
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/NamespaceTest.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.myproject.config.NamespaceConfig;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ */
+public class NamespaceTest {
+ @Test
+ public void verifyConfigClassWithExplicitNamespace() {
+ NamespaceConfig config = new ConfigGetter<>(NamespaceConfig.class).getConfig("raw: a 0\n");
+ assertThat(config.a(), is(0));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/UnicodeTest.java b/config/src/test/java/com/yahoo/config/subscription/UnicodeTest.java
new file mode 100644
index 00000000000..355c94e03b6
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/UnicodeTest.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription;
+
+import com.yahoo.foo.UnicodeConfig;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests reading of a config containing unicode characters in UTF-8
+ *
+ * @author Vidar Larsen
+ * @author Harald Musum
+ */
+public class UnicodeTest {
+ /**
+ * Reads a config from a file which is exactly like one returned from
+ * the config server given only default values for this config.
+ * The parsing code is the same whether the reading happens from file
+ * or from a server connection, so this tests that this config can be
+ * received correctly from the server
+ */
+ @Test
+ public void testUnicodeConfigReading() {
+ ConfigGetter<UnicodeConfig> getter = new ConfigGetter<>(UnicodeConfig.class);
+ UnicodeConfig config = getter.getConfig("file:src/test/resources/configs/unicode/unicode.cfg");
+
+ assertEquals("Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo", config.unicodestring1());
+ assertEquals("abc \u00E6\u00F8\u00E5 \u56F2\u7881 \u00C6\u00D8\u00C5 ABC", config.unicodestring2());
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/impl/FileConfigSubscriptionTest.java b/config/src/test/java/com/yahoo/config/subscription/impl/FileConfigSubscriptionTest.java
new file mode 100644
index 00000000000..ce9323f0ec7
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/impl/FileConfigSubscriptionTest.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription.impl;
+
+import com.yahoo.config.ConfigurationRuntimeException;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.TestReferenceConfig;
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.config.subscription.DirSource;
+import com.yahoo.config.subscription.FileSource;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.TimingValues;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1.7
+ */
+public class FileConfigSubscriptionTest {
+ private File TEST_TYPES_FILE;
+
+ @Before
+ public void setUp() throws IOException {
+ TEST_TYPES_FILE = File.createTempFile("fooconfig", ".cfg");
+ }
+
+ private void writeConfig(String field, String value) throws IOException {
+ FileWriter writer = new FileWriter(TEST_TYPES_FILE);
+ writer.write(field + " " + value);
+ writer.close();
+ }
+
+ @Test
+ public void require_that_new_config_is_detected_in_time() throws IOException, InterruptedException {
+ writeConfig("intval", "23");
+ ConfigSubscriber subscriber = new ConfigSubscriber(new FileSource(TEST_TYPES_FILE));
+ ConfigSubscription<SimpletypesConfig> sub = new FileConfigSubscription<>(
+ new ConfigKey<>(SimpletypesConfig.class, ""),
+ subscriber,
+ TEST_TYPES_FILE);
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.intval(), is(23));
+ Thread.sleep(1000);
+ writeConfig("intval", "33");
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.intval(), is(33));
+ }
+
+ @Test
+ public void require_that_new_config_is_detected_on_reload() throws IOException {
+ writeConfig("intval", "23");
+ ConfigSubscriber subscriber = new ConfigSubscriber(new FileSource(TEST_TYPES_FILE));
+ ConfigSubscription<SimpletypesConfig> sub = new FileConfigSubscription<>(
+ new ConfigKey<>(SimpletypesConfig.class, ""),
+ subscriber,
+ TEST_TYPES_FILE);
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.intval(), is(23));
+ writeConfig("intval", "33");
+ sub.reload(1);
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.intval(), is(33));
+ assertTrue(sub.isConfigChanged());
+ assertTrue(sub.isGenerationChanged());
+ sub.reload(2);
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.intval(), is(33));
+ assertFalse(sub.isConfigChanged());
+ assertTrue(sub.isGenerationChanged());
+ }
+
+ @Test
+ public void require_that_dir_config_id_reference_is_not_changed() {
+ final String cfgDir = "src/test/resources/configs/foo";
+ final String cfgId = "dir:" + cfgDir;
+ final ConfigKey<TestReferenceConfig> key = new ConfigKey<>(TestReferenceConfig.class, cfgId);
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ ConfigSubscription<TestReferenceConfig> sub = ConfigSubscription.get(key, subscriber, new DirSource(new File(cfgDir)), new TimingValues());
+ assertTrue(sub.nextConfig(1000));
+ assertThat(sub.config.configId(), is(cfgId));
+ }
+
+ @Test(expected = ConfigurationRuntimeException.class)
+ public void require_that_bad_file_throws_exception() throws IOException {
+ // A little trick to ensure that we can create the subscriber, but that we get an error when reading.
+ writeConfig("intval", "23");
+ ConfigSubscriber subscriber = new ConfigSubscriber(new FileSource(TEST_TYPES_FILE));
+ ConfigSubscription<SimpletypesConfig> sub = new FileConfigSubscription<>(
+ new ConfigKey<>(SimpletypesConfig.class, ""),
+ subscriber,
+ TEST_TYPES_FILE);
+ sub.reload(1);
+ assertTrue(TEST_TYPES_FILE.setReadable(false));
+ sub.nextConfig(0);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/impl/JRTConfigRequesterTest.java b/config/src/test/java/com/yahoo/config/subscription/impl/JRTConfigRequesterTest.java
new file mode 100644
index 00000000000..7d3b86db2fe
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/impl/JRTConfigRequesterTest.java
@@ -0,0 +1,356 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription.impl;
+
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ErrorCode;
+import com.yahoo.vespa.config.ErrorType;
+import com.yahoo.vespa.config.TimingValues;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1.11
+ */
+public class JRTConfigRequesterTest {
+
+ @Test
+ public void testDelayCalculation() {
+ TimingValues defaultTimingValues = new TimingValues();
+ Random random = new Random(0); // Use seed to make tests predictable
+ TimingValues timingValues = new TimingValues(defaultTimingValues, random);
+
+ // transientFailures and fatalFailures are not set until after delay has been calculated,
+ // so 0 is the case for the first failure
+ int transientFailures = 0;
+ int fatalFailures = 0;
+ boolean configured = false;
+
+ // First time failure, not configured
+ long delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is(timingValues.getUnconfiguredDelay()));
+ transientFailures = 5;
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is((transientFailures + 1) * timingValues.getUnconfiguredDelay()));
+ transientFailures = 0;
+
+
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.FATAL,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertTrue(delay > (1 - JRTConfigRequester.randomFraction) * timingValues.getFixedDelay());
+ assertTrue(delay < (1 + JRTConfigRequester.randomFraction) * timingValues.getFixedDelay());
+ assertThat(delay, is(5462L));
+
+ // First time failure, configured
+ configured = true;
+
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is(timingValues.getConfiguredErrorDelay()));
+
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.FATAL,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertTrue(delay > (1 - JRTConfigRequester.randomFraction) * timingValues.getFixedDelay());
+ assertTrue(delay < (1 + JRTConfigRequester.randomFraction) * timingValues.getFixedDelay());
+ assertThat(delay, is(5663L));
+
+
+ // nth time failure, not configured
+ fatalFailures = 1;
+ configured = false;
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is(timingValues.getUnconfiguredDelay()));
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.FATAL,
+ transientFailures, fatalFailures, timingValues, configured);
+ final long l = timingValues.getFixedDelay() + timingValues.getUnconfiguredDelay();
+ assertTrue(delay > (1 - JRTConfigRequester.randomFraction) * l);
+ assertTrue(delay < (1 + JRTConfigRequester.randomFraction) * l);
+ assertThat(delay, is(5377L));
+
+
+ // nth time failure, configured
+ fatalFailures = 1;
+ configured = true;
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is(timingValues.getConfiguredErrorDelay()));
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.FATAL,
+ transientFailures, fatalFailures, timingValues, configured);
+ final long l1 = timingValues.getFixedDelay() + timingValues.getConfiguredErrorDelay();
+ assertTrue(delay > (1 - JRTConfigRequester.randomFraction) * l1);
+ assertTrue(delay < (1 + JRTConfigRequester.randomFraction) * l1);
+ assertThat(delay, is(20851L));
+
+
+ // 1 more than max delay multiplier time failure, configured
+ fatalFailures = timingValues.getMaxDelayMultiplier() + 1;
+ configured = true;
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, configured);
+ assertThat(delay, is(timingValues.getConfiguredErrorDelay()));
+ assertTrue(delay < timingValues.getMaxDelayMultiplier() * timingValues.getConfiguredErrorDelay());
+ delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.FATAL,
+ transientFailures, fatalFailures, timingValues, configured);
+ final long l2 = timingValues.getFixedDelay() + timingValues.getMaxDelayMultiplier() * timingValues.getConfiguredErrorDelay();
+ assertTrue(delay > (1 - JRTConfigRequester.randomFraction) * l2);
+ assertTrue(delay < (1 + JRTConfigRequester.randomFraction) * l2);
+ assertThat(delay, is(163520L));
+ }
+
+ @Test
+ public void testDelay() {
+ TimingValues timingValues = new TimingValues();
+
+ // transientFailures and fatalFailures are not set until after delay has been calculated,
+ // so 0 is the case for the first failure
+ int transientFailures = 0;
+ int fatalFailures = 0;
+
+ // First time failure, configured
+ long delay = JRTConfigRequester.calculateFailedRequestDelay(ErrorType.TRANSIENT,
+ transientFailures, fatalFailures, timingValues, true);
+ assertThat(delay, is(timingValues.getConfiguredErrorDelay()));
+ assertThat(delay, is((transientFailures + 1) * timingValues.getConfiguredErrorDelay()));
+ }
+
+ @Test
+ public void testErrorTypes() {
+ List<Integer> transientErrors = Arrays.asList(com.yahoo.jrt.ErrorCode.CONNECTION, com.yahoo.jrt.ErrorCode.TIMEOUT);
+ List<Integer> fatalErrors = Arrays.asList(ErrorCode.UNKNOWN_CONFIG, ErrorCode.UNKNOWN_DEFINITION, ErrorCode.OUTDATED_CONFIG,
+ ErrorCode.UNKNOWN_DEF_MD5, ErrorCode.ILLEGAL_NAME, ErrorCode.ILLEGAL_VERSION, ErrorCode.ILLEGAL_CONFIGID,
+ ErrorCode.ILLEGAL_DEF_MD5, ErrorCode.ILLEGAL_CONFIG_MD5, ErrorCode.ILLEGAL_TIMEOUT, ErrorCode.INTERNAL_ERROR,
+ 9999); // unknown should also be fatal
+ for (Integer i : transientErrors) {
+ assertThat(ErrorType.getErrorType(i), is(ErrorType.TRANSIENT));
+ }
+ for (Integer i : fatalErrors) {
+ assertThat(ErrorType.getErrorType(i), is(ErrorType.FATAL));
+ }
+ }
+
+ @Test
+ public void testFirstRequestAfterSubscribing() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+
+ final MockConnection connection = new MockConnection();
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ assertThat(requester.getConnectionPool(), is(connection));
+ requester.request(sub);
+ final Request request = connection.getRequest();
+ assertNotNull(request);
+ assertThat(connection.getNumberOfRequests(), is(1));
+ JRTServerConfigRequestV3 receivedRequest = JRTServerConfigRequestV3.createFromRequest(request);
+ assertTrue(receivedRequest.validateParameters());
+ assertThat(receivedRequest.getTimeout(), is(timingValues.getSubscribeTimeout()));
+ assertThat(requester.getFatalFailures(), is(0));
+ assertThat(requester.getTransientFailures(), is(0));
+ }
+
+ @Test
+ public void testFatalError() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+
+ final MockConnection connection = new MockConnection(new ErrorResponseHandler());
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(createSubscription(subscriber, timingValues));
+ waitUntilResponse(connection);
+ assertThat(requester.getFatalFailures(), is(1));
+ assertThat(requester.getTransientFailures(), is(0));
+ }
+
+ @Test
+ public void testFatalErrorSubscribed() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+ sub.setConfig(config());
+ sub.setGeneration(1L);
+
+ final MockConnection connection = new MockConnection(new ErrorResponseHandler());
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(sub);
+ waitUntilResponse(connection);
+ assertThat(requester.getFatalFailures(), is(1));
+ assertThat(requester.getTransientFailures(), is(0));
+ }
+
+ @Test
+ public void testTransientError() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+
+ final MockConnection connection = new MockConnection(new ErrorResponseHandler(com.yahoo.jrt.ErrorCode.TIMEOUT));
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(createSubscription(subscriber, timingValues));
+ waitUntilResponse(connection);
+ assertThat(requester.getFatalFailures(), is(0));
+ assertThat(requester.getTransientFailures(), is(1));
+ }
+
+ @Test
+ public void testTransientErrorSubscribed() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+ sub.setConfig(config());
+ sub.setGeneration(1L);
+
+ final MockConnection connection = new MockConnection(new ErrorResponseHandler(com.yahoo.jrt.ErrorCode.TIMEOUT));
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(sub);
+ waitUntilResponse(connection);
+ assertThat(requester.getFatalFailures(), is(0));
+ assertThat(requester.getTransientFailures(), is(1));
+ }
+
+ @Test
+ public void testUnknownConfigDefinitionError() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+ sub.setConfig(config());
+ sub.setGeneration(1L);
+
+ final MockConnection connection = new MockConnection(new ErrorResponseHandler(ErrorCode.UNKNOWN_DEFINITION));
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ assertThat(requester.getConnectionPool(), is(connection));
+ requester.request(sub);
+ waitUntilResponse(connection);
+ assertThat(requester.getFatalFailures(), is(1));
+ assertThat(requester.getTransientFailures(), is(0));
+ // TODO Check that no further request was sent?
+ }
+
+ @Test
+ public void testClosedSubscription() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+ sub.close();
+
+ final MockConnection connection = new MockConnection(new MockConnection.OKResponseHandler());
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(sub);
+ assertThat(connection.getNumberOfRequests(), is(1));
+ // Check that no further request was sent?
+ try {
+ Thread.sleep(timingValues.getFixedDelay()*2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertThat(connection.getNumberOfRequests(), is(1));
+ }
+
+ @Test
+ public void testTimeout() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ final TimingValues timingValues = getTestTimingValues();
+ JRTConfigSubscription<SimpletypesConfig> sub = createSubscription(subscriber, timingValues);
+ sub.close();
+
+ final MockConnection connection = new MockConnection(
+ new DelayedResponseHandler(timingValues.getSubscribeTimeout()),
+ 2); // fake that we have more than one source
+ JRTConfigRequester requester = new JRTConfigRequester(connection, timingValues);
+ requester.request(createSubscription(subscriber, timingValues));
+ // Check that no further request was sent?
+ try {
+ Thread.sleep(timingValues.getFixedDelay()*2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertTrue(connection.getNumberOfFailovers() >= 1);
+ }
+
+ private JRTConfigSubscription<SimpletypesConfig> createSubscription(ConfigSubscriber subscriber, TimingValues timingValues) {
+ return new JRTConfigSubscription<>(
+ new ConfigKey<>(SimpletypesConfig.class, "testid"), subscriber, null, timingValues);
+ }
+
+ private SimpletypesConfig config() {
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ return new SimpletypesConfig(builder);
+ }
+
+ private void waitUntilResponse(MockConnection connection) {
+ int i = 0;
+ while (i < 1000 && connection.getRequest() == null) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ i++;
+ }
+ }
+
+ public static TimingValues getTestTimingValues() { return new TimingValues(
+ 1000, // successTimeout
+ 500, // errorTimeout
+ 500, // initialTimeout
+ 2000, // subscribeTimeout
+ 250, // unconfiguredDelay
+ 500, // configuredErrorDelay
+ 250, // fixedDelay
+ 5); // maxDelayMultiplier
+ }
+
+ private static class ErrorResponseHandler extends MockConnection.OKResponseHandler {
+ private final int errorCode;
+
+ public ErrorResponseHandler() {
+ this(ErrorCode.INTERNAL_ERROR);
+ }
+
+ public ErrorResponseHandler(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public void run() {
+ System.out.println("Running error response handler");
+ request().setError(errorCode, "error");
+ requestWaiter().handleRequestDone(request());
+ }
+ }
+
+ private static class DelayedResponseHandler extends MockConnection.OKResponseHandler {
+ private final long waitTimeMilliSeconds;
+
+ public DelayedResponseHandler(long waitTimeMilliSeconds) {
+ this.waitTimeMilliSeconds = waitTimeMilliSeconds;
+ }
+
+ @Override
+ public void run() {
+ System.out.println("Running delayed response handler (waiting " + waitTimeMilliSeconds +
+ ") before responding");
+ try {
+ Thread.sleep(waitTimeMilliSeconds);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ request().setError(com.yahoo.jrt.ErrorCode.TIMEOUT, "error");
+ requestWaiter().handleRequestDone(request());
+ }
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/config/subscription/util/JsonHelper.java b/config/src/test/java/com/yahoo/config/subscription/util/JsonHelper.java
new file mode 100644
index 00000000000..27ac1a1278c
--- /dev/null
+++ b/config/src/test/java/com/yahoo/config/subscription/util/JsonHelper.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.subscription.util;
+
+import com.google.common.base.Joiner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
+
+/**
+ * @author Vegard Sjonfjell
+ */
+public class JsonHelper {
+ /**
+ * Convenience method to input JSON without escaping double quotes and newlines
+ * Each parameter represents a line of JSON encoded data
+ * The lines are joined with newline and single quotes are replaced with double quotes
+ */
+ public static String inputJson(String... lines) {
+ return Joiner.on("\n").join(lines).replaceAll("'", "\"");
+ }
+
+ /**
+ * Structurally compare two JSON encoded strings
+ */
+ public static void assertJsonEquals(String inputJson, String expectedJson) {
+ assertThat(inputJson, sameJSONAs(expectedJson));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java
new file mode 100644
index 00000000000..95ed049de6d
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigBuilderMergeTest.java
@@ -0,0 +1,120 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.foo.ArraytypesConfig;
+import com.yahoo.config.subscription.ConfigInstanceUtil;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.StructtypesConfig;
+import com.yahoo.foo.MaptypesConfig;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static com.yahoo.foo.MaptypesConfig.Innermap;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * SEO keywords: test override() on builders. overrideTest, testOverride
+ *
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigBuilderMergeTest {
+
+ private SimpletypesConfig.Builder createSimpleBuilder(String s, int i, long l, double d, boolean b) {
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ builder.stringval(s);
+ builder.intval(i);
+ builder.longval(l);
+ builder.doubleval(d);
+ builder.boolval(b);
+ return builder;
+ }
+
+ private ArraytypesConfig.Builder createArrayBuilder(String [] strings) {
+ ArraytypesConfig.Builder builder = new ArraytypesConfig.Builder();
+ for (String str : strings) {
+ builder.stringarr(str);
+ }
+ return builder;
+ }
+
+ private StructtypesConfig.Builder createSimpleStructBuilder(String name, String gender, String [] emails) {
+ StructtypesConfig.Builder builder = new StructtypesConfig.Builder();
+ StructtypesConfig.Simple.Builder simpleBuilder = new StructtypesConfig.Simple.Builder();
+ simpleBuilder.name(name);
+ simpleBuilder.gender(StructtypesConfig.Simple.Gender.Enum.valueOf(gender));
+ simpleBuilder.emails(Arrays.asList(emails));
+ builder.simple(simpleBuilder);
+ return builder;
+ }
+
+ @Test
+ public void require_that_simple_fields_are_overwritten_on_merge() {
+ SimpletypesConfig.Builder b1 = createSimpleBuilder("foo", 2, 5, 4.3, false);
+ SimpletypesConfig.Builder b2 = createSimpleBuilder("bar", 3, 6, 3.3, true);
+ ConfigInstanceUtil.setValues(b1, b2);
+ SimpletypesConfig c1 = new SimpletypesConfig(b1);
+ SimpletypesConfig c2 = new SimpletypesConfig(b2);
+ assertThat(c1, is(c2));
+ }
+
+ @Test
+ public void require_that_arrays_are_appended_on_merge() {
+ ArraytypesConfig.Builder b1 = createArrayBuilder(new String[] { "foo", "bar" });
+ ArraytypesConfig.Builder b2 = createArrayBuilder(new String[] { "baz", "bim" });
+
+ ConfigInstanceUtil.setValues(b1, b2);
+ ArraytypesConfig c1 = new ArraytypesConfig(b1);
+ assertThat(c1.stringarr().size(), is(4));
+ assertThat(c1.stringarr(0), is("foo"));
+ assertThat(c1.stringarr(1), is("bar"));
+ assertThat(c1.stringarr(2), is("baz"));
+ assertThat(c1.stringarr(3), is("bim"));
+
+ ArraytypesConfig c2 = new ArraytypesConfig(b2);
+ assertThat(c2.stringarr(0), is("baz"));
+ assertThat(c2.stringarr(1), is("bim"));
+ }
+
+ @Test
+ public void require_that_struct_fields_are_overwritten() {
+ String name1 = "foo";
+ String gender1 = "MALE";
+ String emails1[] = { "foo@bar", "bar@foo" };
+ String name2 = "bar";
+ String gender2 = "FEMALE";
+ String emails2[] = { "foo@bar", "bar@foo" };
+ StructtypesConfig.Builder b1 = createSimpleStructBuilder(name1, gender1, emails1);
+ StructtypesConfig.Builder b2 = createSimpleStructBuilder(name2, gender2, emails2);
+ ConfigInstanceUtil.setValues(b1, b2);
+ StructtypesConfig c1 = new StructtypesConfig(b1);
+ assertThat(c1.simple().name(), is(name2));
+ assertThat(c1.simple().gender().toString(), is(gender2));
+ assertThat(c1.simple().emails(0), is(emails2[0]));
+ assertThat(c1.simple().emails(1), is(emails2[1]));
+ }
+
+ @Test
+ public void source_map_is_copied_into_destination_map_on_merge() {
+ MaptypesConfig.Builder destination = new MaptypesConfig.Builder()
+ .intmap("one", 1)
+ .innermap("first", new Innermap.Builder()
+ .foo(1));
+
+ MaptypesConfig.Builder source = new MaptypesConfig.Builder()
+ .intmap("two", 2)
+ .innermap("second", new Innermap.Builder()
+ .foo(2));
+
+ ConfigInstanceUtil.setValues(destination, source);
+
+ MaptypesConfig config = new MaptypesConfig(destination);
+ assertThat(config.intmap("one"), is(1));
+ assertThat(config.intmap("two"), is(2));
+ assertThat(config.innermap("first").foo(), is(1));
+ assertThat(config.innermap("second").foo(), is(2));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java
new file mode 100755
index 00000000000..5356acdfade
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigCacheKeyTest.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigCacheKeyTest {
+ @Test
+ public void testConfigCacheKey() {
+ final String defMd5 = "md5";
+ final String defMd5_2 = "md5_2";
+
+ ConfigCacheKey k1 = new ConfigCacheKey("foo", "id", "ns", defMd5);
+ ConfigCacheKey k2 = new ConfigCacheKey("foo", "id", "ns", defMd5);
+ ConfigCacheKey k3 = new ConfigCacheKey("foo", "id", "ns", defMd5_2);
+ ConfigCacheKey k4 = new ConfigCacheKey("foo", "id", "ns_1", defMd5);
+ ConfigCacheKey k5 = new ConfigCacheKey("foo", "id", "ns_1", null); // test with null defMd5
+ final ConfigKey<?> configKey = new ConfigKey<>("foo", "id", "ns");
+ ConfigCacheKey k1_2 = new ConfigCacheKey(configKey, defMd5);
+ assertTrue(k1.equals(k1));
+ assertTrue(k1.equals(k1_2));
+ assertTrue(k1.equals(k2));
+ assertFalse(k3.equals(k2));
+ assertFalse(k4.equals(k1));
+ assertThat(k1.hashCode(), is(k2.hashCode()));
+ assertThat(k1.getDefMd5(), is(defMd5));
+ assertThat(k1.toString(), is(configKey.toString() + "," + defMd5));
+ assertThat(k5.hashCode(), is(not(k1.hashCode())));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java
new file mode 100644
index 00000000000..cb1cccc0139
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java
@@ -0,0 +1,141 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.codegen.CNode;
+import com.yahoo.config.codegen.DefParser;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+
+/**
+ * Unit tests for ConfigDefinitionBuilder.
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigDefinitionBuilderTest {
+
+ private static final String TEST_DIR = "src/test/resources/configs/def-files";
+ private static final String DEF_NAME = TEST_DIR + "/function-test.def";
+
+
+ @Test
+ // TODO Test ranges
+ public void testCreateConfigDefinition() throws IOException, InterruptedException {
+ File defFile = new File(DEF_NAME);
+ DefParser defParser = new DefParser(defFile.getName(), new FileReader(defFile));
+ CNode root = defParser.getTree();
+
+ ConfigDefinition def = ConfigDefinitionBuilder.createConfigDefinition(root);
+
+ assertNotNull(def);
+ assertThat(def.getBoolDefs().size(), is(2));
+ assertNull(def.getBoolDefs().get("bool_val").getDefVal());
+ assertThat(def.getBoolDefs().get("bool_with_def").getDefVal(), is(false));
+
+ assertThat(def.getIntDefs().size(), is(2));
+ assertNull(def.getIntDefs().get("int_val").getDefVal());
+ assertThat(def.getIntDefs().get("int_with_def").getDefVal(), is(-545));
+
+ assertThat(def.getLongDefs().size(), is(2));
+ assertNull(def.getLongDefs().get("long_val").getDefVal());
+ assertThat(def.getLongDefs().get("long_with_def").getDefVal(), is(-50000000000L));
+
+ assertThat(def.getDoubleDefs().size(), is(2));
+ assertNull(def.getDoubleDefs().get("double_val").getDefVal());
+ assertThat(def.getDoubleDefs().get("double_with_def").getDefVal(), is(-6.43));
+
+ assertThat(def.getEnumDefs().size(), is(3));
+ assertTrue(def.getEnumDefs().containsKey("enum_val"));
+ assertThat(def.getEnumDefs().get("enum_val").getVals().size(), is(3));
+ assertThat(def.getEnumDefs().get("enum_val").getVals().get(0), is("FOO"));
+ assertThat(def.getEnumDefs().get("enum_val").getVals().get(1), is("BAR"));
+ assertThat(def.getEnumDefs().get("enum_val").getVals().get(2), is("FOOBAR"));
+
+ assertTrue(def.getEnumDefs().containsKey("enumwithdef"));
+ assertThat(def.getEnumDefs().get("enumwithdef").getDefVal(), is("BAR2"));
+
+ assertTrue(def.getEnumDefs().containsKey("onechoice"));
+ assertThat(def.getEnumDefs().get("onechoice").getDefVal(), is("ONLYFOO"));
+
+ assertThat(def.getStringDefs().size(), is(2));
+ assertNull(def.getStringDefs().get("string_val").getDefVal()); // The return value is a String, so null if no default value
+ assertThat(def.getStringDefs().get("stringwithdef").getDefVal(), is("foobar"));
+
+ assertThat(def.getReferenceDefs().size(), is(2));
+ assertNotNull(def.getReferenceDefs().get("refval"));
+ assertThat(def.getReferenceDefs().get("refwithdef").getDefVal(), is(":parent:"));
+
+ assertThat(def.getFileDefs().size(), is(1));
+ assertNotNull(def.getFileDefs().get("fileVal"));
+
+ assertThat(def.getArrayDefs().size(), is(9));
+ assertNotNull(def.getArrayDefs().get("boolarr"));
+ assertThat(def.getArrayDefs().get("boolarr").getTypeSpec().getType(), is("bool"));
+
+ assertNotNull(def.getArrayDefs().get("enumarr"));
+ assertThat(def.getArrayDefs().get("enumarr").getTypeSpec().getType(), is("enum"));
+ assertThat(def.getArrayDefs().get("enumarr").getTypeSpec().getEnumVals().toString(), is("[ARRAY, VALUES]"));
+
+ assertNotNull(def.getArrayDefs().get("refarr"));
+ assertThat(def.getArrayDefs().get("refarr").getTypeSpec().getType(), is("reference"));
+
+ assertNotNull(def.getArrayDefs().get("fileArr"));
+ assertThat(def.getArrayDefs().get("fileArr").getTypeSpec().getType(), is("file"));
+
+ assertThat(def.getStructDefs().size(), is(2));
+ assertNotNull(def.getStructDefs().get("basicStruct"));
+ assertThat(def.getStructDefs().get("basicStruct").getStringDefs().size(), is(1));
+ assertThat(def.getStructDefs().get("basicStruct").getStringDefs().get("foo").getDefVal(), is("basic"));
+ assertThat(def.getStructDefs().get("basicStruct").getIntDefs().size(), is(1));
+ assertNull(def.getStructDefs().get("basicStruct").getIntDefs().get("bar").getDefVal());
+ assertThat(def.getStructDefs().get("basicStruct").getArrayDefs().size(), is(1));
+ assertThat(def.getStructDefs().get("basicStruct").getArrayDefs().get("intArr").getTypeSpec().getType(), is("int"));
+
+ assertNotNull(def.getStructDefs().get("rootStruct"));
+ assertNotNull(def.getStructDefs().get("rootStruct").getStructDefs().get("inner0"));
+ assertNotNull(def.getStructDefs().get("rootStruct").getStructDefs().get("inner1"));
+ assertThat(def.getStructDefs().get("rootStruct").getInnerArrayDefs().size(), is(1));
+ assertNotNull(def.getStructDefs().get("rootStruct").getInnerArrayDefs().get("innerArr"));
+ assertThat(def.getStructDefs().get("rootStruct").getInnerArrayDefs().get("innerArr").getStringDefs().size(), is(1));
+
+ assertThat(def.getInnerArrayDefs().size(), is(1));
+ assertNotNull(def.getInnerArrayDefs().get("myarray"));
+ assertThat(def.getInnerArrayDefs().get("myarray").getIntDefs().get("intval").getDefVal(), is(14));
+ assertThat(def.getInnerArrayDefs().get("myarray").getArrayDefs().size(), is(1));
+ assertNotNull(def.getInnerArrayDefs().get("myarray").getArrayDefs().get("stringval"));
+ assertThat(def.getInnerArrayDefs().get("myarray").getArrayDefs().get("stringval").getTypeSpec().getType(), is("string"));
+ assertThat(def.getInnerArrayDefs().get("myarray").getEnumDefs().get("enumval").getDefVal(), is("TYPE"));
+ assertNull(def.getInnerArrayDefs().get("myarray").getReferenceDefs().get("refval").getDefVal());
+ assertThat(def.getInnerArrayDefs().get("myarray").getInnerArrayDefs().size(), is(1));
+ assertThat(def.getInnerArrayDefs().get("myarray").getInnerArrayDefs().get("anotherarray").getIntDefs().get("foo").getDefVal(), is(-4));
+ assertNull(def.getInnerArrayDefs().get("myarray").getStructDefs().get("myStruct").getIntDefs().get("a").getDefVal());
+ assertThat(def.getInnerArrayDefs().get("myarray").getStructDefs().get("myStruct").getIntDefs().get("b").getDefVal(), is(2));
+
+ // Maps
+ assertEquals(def.getLeafMapDefs().size(), 4);
+ assertEquals(def.getLeafMapDefs().get("intMap").getTypeSpec().getType(), "int");
+ assertEquals(def.getLeafMapDefs().get("stringMap").getTypeSpec().getType(), "string");
+ assertEquals(def.getStructMapDefs().size(), 1);
+ assertEquals(def.getStructMapDefs().get("myStructMap").getIntDefs().get("myInt").getDefVal(), null);
+ assertEquals(def.getStructMapDefs().get("myStructMap").getStringDefs().get("myString").getDefVal(), null);
+ assertEquals(def.getStructMapDefs().get("myStructMap").getIntDefs().get("myIntDef").getDefVal(), (Integer)56);
+ assertEquals(def.getStructMapDefs().get("myStructMap").getStringDefs().get("myStringDef").getDefVal(), "g");
+
+ }
+
+ @Test
+ public void testCreateConfigDefinitionOverrideNamespace() throws IOException, InterruptedException {
+ File defFile = new File(DEF_NAME);
+ DefParser defParser = new DefParser(defFile.getName(), new FileReader(defFile));
+ CNode root = defParser.getTree();
+
+ ConfigDefinition def = ConfigDefinitionBuilder.createConfigDefinition(root, "foo");
+ assertThat(def.getNamespace(), is("foo"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java
new file mode 100644
index 00000000000..fbdca4af6df
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionKeyTest.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Tests ConfigDefinitionKey
+ *
+ * @author musum
+ */
+public class ConfigDefinitionKeyTest {
+
+ @Test
+ public void testBasic() {
+ ConfigDefinitionKey def1 = new ConfigDefinitionKey("foo", "");
+ ConfigDefinitionKey def2 = new ConfigDefinitionKey("foo", "bar");
+
+ assertThat(def1.getName(), is("foo"));
+ assertThat(def1.getNamespace(), is(""));
+
+ assertTrue(def1.equals(def1));
+ assertFalse(def1.equals(def2));
+ assertFalse(def1.equals(new Object()));
+ assertTrue(def2.equals(def2));
+ }
+
+ @Test
+ public void testCreationFromConfigKey() {
+ ConfigKey<?> key1 = new ConfigKey<>("foo", "id", "bar");
+ ConfigDefinitionKey def1 = new ConfigDefinitionKey(key1);
+
+ assertThat(def1.getName(), is(key1.getName()));
+ assertThat(def1.getNamespace(), is(key1.getNamespace()));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java
new file mode 100644
index 00000000000..a00c013109e
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionSetTest.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Class to hold config definitions and resolving requests for the correct definition
+ *
+ * @author Harald Musum <musum@yahoo-inc.com>
+ * @since 2011-11-18
+ */
+public class ConfigDefinitionSetTest {
+
+ @Test
+ public void testBasic() {
+ ConfigDefinitionSet configDefinitionSet = new ConfigDefinitionSet();
+ ConfigDefinition def1 = new ConfigDefinition("foo", "1");
+ ConfigDefinition def2 = new ConfigDefinition("foo", "1", "namespace1");
+ ConfigDefinition def3 = new ConfigDefinition("foo", "1", "namespace2");
+ final ConfigDefinitionKey key1 = new ConfigDefinitionKey(def1.getName(), def1.getNamespace());
+ configDefinitionSet.add(key1, def1);
+ ConfigDefinitionKey key2 = new ConfigDefinitionKey(def2.getName(), def2.getNamespace());
+ configDefinitionSet.add(key2, def2);
+ ConfigDefinitionKey key3 = new ConfigDefinitionKey(def3.getName(), def3.getNamespace());
+ configDefinitionSet.add(key3, def3);
+ assertThat(configDefinitionSet.size(), is(3));
+ assertThat(configDefinitionSet.get(key1), is(def1));
+ assertThat(configDefinitionSet.get(key2), is(def2));
+ assertThat(configDefinitionSet.get(key3), is(def3));
+
+ String str = configDefinitionSet.toString();
+ assertTrue(str.contains("namespace1.foo"));
+ assertTrue(str.contains("namespace2.foo"));
+ assertTrue(str.contains("config.foo"));
+ }
+
+ @Test
+ public void testFallbackToDefaultNamespace() {
+ ConfigDefinitionSet configDefinitionSet = new ConfigDefinitionSet();
+ ConfigDefinition def1 = new ConfigDefinition("foo", "1");
+ ConfigDefinition def2 = new ConfigDefinition("bar", "1", "namespace");
+
+ configDefinitionSet.add(new ConfigDefinitionKey(def1.getName(), def1.getNamespace()), def1);
+ configDefinitionSet.add(new ConfigDefinitionKey(def2.getName(), def2.getNamespace()), def2);
+
+ // fallback to default namespace
+ assertThat(configDefinitionSet.get(new ConfigDefinitionKey("foo", "namespace")), is(def1));
+ // Should not fallback to some other config with same name, but different namespace (not default namespace)
+ assertNull(configDefinitionSet.get(new ConfigDefinitionKey("bar", "someothernamespace")));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java
new file mode 100755
index 00000000000..ce03d5f6823
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionTest.java
@@ -0,0 +1,234 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import com.yahoo.vespa.config.ConfigDefinition.EnumDef;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for ConfigDefinition.
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigDefinitionTest {
+
+ @Test
+ public void testVersionComparator() {
+ Comparator<String> c = new ConfigDefinition.VersionComparator();
+
+ assertEquals(0, c.compare("1", "1"));
+ assertEquals(0, c.compare("1-0", "1"));
+ assertEquals(0, c.compare("1-0-0", "1"));
+ assertEquals(0, c.compare("1-0-0", "1-0"));
+ assertEquals(0, c.compare("0-1-1", "0-1-1"));
+ assertEquals(0, c.compare("0-1-0", "0-1"));
+ assertEquals(-1, c.compare("0", "1"));
+ assertEquals(-1, c.compare("0-1-0", "0-1-1"));
+ assertEquals(-1, c.compare("0-1-0", "1-1-1"));
+ assertEquals(-1, c.compare("0-0-1", "0-1"));
+ assertEquals(1, c.compare("0-1-1", "0-1-0"));
+ assertEquals(1, c.compare("1-1-1", "0-1-0"));
+ assertEquals(1, c.compare("0-1", "0-0-1"));
+ assertEquals(1, c.compare("1-1", "1"));
+
+ List<String> versions = Arrays.asList("25", "5", "1-1", "0-2-3", "1", "1-0");
+ Collections.sort(versions, new ConfigDefinition.VersionComparator());
+ List<String> solution = Arrays.asList("0-2-3", "1", "1-0", "1-1", "5", "25");
+ assertEquals(solution, versions);
+ }
+
+ @Test
+ public void testDefNumberCompare() {
+ ConfigDefinition df1 = new ConfigDefinition("d", "25");
+ ConfigDefinition df2 = new ConfigDefinition("d", "5");
+ ConfigDefinition df3 = new ConfigDefinition("d", "1-1");
+ ConfigDefinition df4 = new ConfigDefinition("d", "0-2-3");
+ ConfigDefinition df5 = new ConfigDefinition("d", "1");
+ ConfigDefinition df6 = new ConfigDefinition("d", "1-0");
+ assertTrue(df1.compareTo(df2) > 0);
+ assertTrue(df2.compareTo(df4) > 0);
+ assertEquals(1, df3.compareTo(df4));
+ assertEquals(-1, df4.compareTo(df5));
+ assertEquals(0, df5.compareTo(df6));
+ }
+
+ @Test
+ public void testIntDefaultValues() {
+ ConfigDefinition def = new ConfigDefinition("foo", "1");
+
+ def.addIntDef("foo");
+ def.addIntDef("bar", 0);
+ def.addIntDef("baz", 1);
+ def.addIntDef("xyzzy", 2, 0, null);
+
+ assertNull(def.getIntDefs().get("foo").getDefVal());
+ assertThat(def.getIntDefs().get("foo").getMin(), is(ConfigDefinition.INT_MIN));
+ assertThat(def.getIntDefs().get("foo").getMax(), is(ConfigDefinition.INT_MAX));
+ assertThat(def.getIntDefs().get("bar").getDefVal(), is(0));
+ assertThat(def.getIntDefs().get("baz").getDefVal(), is(1));
+
+ assertThat(def.getIntDefs().get("xyzzy").getDefVal(), is(2));
+ assertThat(def.getIntDefs().get("xyzzy").getMin(), is(0));
+ assertThat(def.getIntDefs().get("xyzzy").getMax(), is(ConfigDefinition.INT_MAX));
+ }
+
+ @Test
+ public void testLongDefaultValues() {
+ ConfigDefinition def = new ConfigDefinition("foo", "1");
+
+ def.addLongDef("foo");
+ def.addLongDef("bar", 1234567890123L);
+ def.addLongDef("xyzzy", 2L, 0L, null);
+
+ assertNull(def.getLongDefs().get("foo").getDefVal());
+ assertThat(def.getLongDefs().get("foo").getMin(), is(ConfigDefinition.LONG_MIN));
+ assertThat(def.getLongDefs().get("foo").getMax(), is(ConfigDefinition.LONG_MAX));
+ assertThat(def.getLongDefs().get("bar").getDefVal(), is(1234567890123L));
+
+ assertThat(def.getLongDefs().get("xyzzy").getDefVal(), is(2L));
+ assertThat(def.getLongDefs().get("xyzzy").getMin(), is(0L));
+ assertThat(def.getLongDefs().get("xyzzy").getMax(), is(ConfigDefinition.LONG_MAX));
+ }
+
+ @Test
+ @SuppressWarnings("serial")
+ public void testDefaultsPayloadMap() {
+ ConfigDefinition def = new ConfigDefinition("foo", "1");
+ def.addStringDef("mystring");
+ def.addStringDef("mystringdef", "foo");
+ def.addBoolDef("mybool");
+ def.addBoolDef("mybooldef", true);
+ def.addIntDef("myint");
+ def.addIntDef("myintdef", 1);
+ def.addLongDef("mylong");
+ def.addLongDef("mylongdef", 11l);
+ def.addDoubleDef("mydouble");
+ def.addDoubleDef("mydoubledef", 2d);
+ EnumDef ed = new EnumDef(new ArrayList<String>(){{add("a1"); add("a2");}}, null);
+ EnumDef eddef = new EnumDef(new ArrayList<String>(){{add("a11"); add("a22");}}, "a22");
+ def.addEnumDef("myenum", ed);
+ def.addEnumDef("myenumdef", eddef);
+ def.addReferenceDef("myref");
+ def.addReferenceDef("myrefdef", "reff");
+ def.addFileDef("myfile");
+ def.addFileDef("myfiledef", "etc");
+ }
+
+ @Test
+ public void testVerification() {
+ ConfigDefinition def = new ConfigDefinition("foo", "1", "bar");
+ def.addBoolDef("boolval");
+ def.addStringDef("stringval");
+ def.addIntDef("intval");
+ def.addLongDef("longval");
+ def.addDoubleDef("doubleval");
+ def.addEnumDef("enumval", new EnumDef(Arrays.asList("FOO"), "FOO"));
+ def.addReferenceDef("refval");
+ def.addFileDef("fileval");
+ def.addInnerArrayDef("innerarr");
+ def.addLeafMapDef("leafmap");
+ ConfigDefinition.ArrayDef intArray = def.arrayDef("intArray");
+ intArray.setTypeSpec(new ConfigDefinition.TypeSpec("intArray", "int", null, null, Integer.MIN_VALUE, Integer.MAX_VALUE));
+
+ ConfigDefinition.ArrayDef longArray = def.arrayDef("longArray");
+ longArray.setTypeSpec(new ConfigDefinition.TypeSpec("longArray", "long", null, null, Long.MIN_VALUE, Long.MAX_VALUE));
+
+ ConfigDefinition.ArrayDef doubleArray = def.arrayDef("doubleArray");
+ doubleArray.setTypeSpec(new ConfigDefinition.TypeSpec("doubleArray", "double", null, null, Double.MIN_VALUE, Double.MAX_VALUE));
+
+ ConfigDefinition.ArrayDef enumArray = def.arrayDef("enumArray");
+ enumArray.setTypeSpec(new ConfigDefinition.TypeSpec("enumArray", "enum", null, "VALID", null, null));
+
+ ConfigDefinition.ArrayDef stringArray = def.arrayDef("stringArray");
+ stringArray.setTypeSpec(new ConfigDefinition.TypeSpec("stringArray", "string", null, null, null, null));
+
+ def.structDef("struct");
+
+ assertVerify(def, "boolval", "true", 0);
+ assertVerify(def, "boolval", "false", 0);
+ assertVerify(def, "boolval", "invalid", IllegalArgumentException.class);
+
+
+ assertVerify(def, "stringval", "foobar", 0);
+ assertVerify(def, "stringval", "foobar", 0);
+ assertVerify(def, "intval", "123", 0);
+ assertVerify(def, "intval", "foobar", IllegalArgumentException.class);
+ assertVerify(def, "longval", "1234", 0);
+ assertVerify(def, "longval", "foobar", IllegalArgumentException.class);
+ assertVerify(def, "doubleval", "foobar", IllegalArgumentException.class);
+ assertVerify(def, "doubleval", "3", 0);
+ assertVerify(def, "doubleval", "3.14", 0);
+ assertVerify(def, "enumval", "foobar", IllegalArgumentException.class);
+ assertVerify(def, "enumval", "foo", IllegalArgumentException.class);
+ assertVerify(def, "enumval", "FOO", 0);
+ assertVerify(def, "refval", "foobar", 0);
+ assertVerify(def, "fileval", "foobar", 0);
+
+ assertVerifyComplex(def, "innerarr", 0);
+ assertVerifyComplex(def, "leafmap", 0);
+ assertVerifyComplex(def, "intArray", 0);
+ assertVerifyComplex(def, "longArray", 0);
+ assertVerifyComplex(def, "doubleArray", 0);
+ assertVerifyComplex(def, "enumArray", 0);
+ assertVerifyComplex(def, "stringArray", 0);
+ assertVerifyArray(intArray, "1345", 0, 0);
+ assertVerifyArray(intArray, "invalid", 0, IllegalArgumentException.class);
+ assertVerifyArray(longArray, "1345", 0, 0);
+ assertVerifyArray(longArray, "invalid", 0, IllegalArgumentException.class);
+ assertVerifyArray(doubleArray, "1345", 0, 0);
+ assertVerifyArray(doubleArray, "1345.3", 0, 0);
+ assertVerifyArray(doubleArray, "invalid", 0, IllegalArgumentException.class);
+ assertVerifyArray(enumArray, "valid", 0, IllegalArgumentException.class);
+ assertVerifyArray(enumArray, "VALID", 0, 0);
+ assertVerifyArray(enumArray, "inVALID", 0, IllegalArgumentException.class);
+ assertVerifyArray(stringArray, "VALID", 0, 0);
+ assertVerifyComplex(def, "struct", 0);
+ }
+
+ private void assertVerifyArray(ConfigDefinition.ArrayDef def, String val, int index, int expectedNumWarnings) {
+ List<String> issuedWarnings = new ArrayList<>();
+ def.verify(val, index, issuedWarnings);
+ assertThat(issuedWarnings.size(), is(expectedNumWarnings));
+ }
+
+ private void assertVerifyArray(ConfigDefinition.ArrayDef def, String val, int index, Class<?> expectedException) {
+ try {
+ def.verify(val, index, new ArrayList<String>());
+ } catch (Exception e) {
+ if (!(e.getClass().isAssignableFrom(expectedException))) {
+ throw e;
+ }
+ }
+ }
+
+ private void assertVerify(ConfigDefinition def, String id, String val, int expectedNumWarnings) {
+ List<String> issuedWarnings = new ArrayList<>();
+ def.verify(id, val, issuedWarnings);
+ assertThat(issuedWarnings.size(), is(expectedNumWarnings));
+ }
+
+ private void assertVerify(ConfigDefinition def, String id, String val, Class<?> expectedException) {
+ try {
+ def.verify(id, val, new ArrayList<String>());
+ } catch (Exception e) {
+ if (!(e.getClass().isAssignableFrom(expectedException))) {
+ throw e;
+ }
+ }
+ }
+
+ private void assertVerifyComplex(ConfigDefinition def, String id, int expectedNumWarnings) {
+ List<String> issuedWarnings = new ArrayList<>();
+ def.verify(id, issuedWarnings);
+ assertThat(issuedWarnings.size(), is(expectedNumWarnings));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java
new file mode 100644
index 00000000000..0e55714ca53
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigFileFormatterTest.java
@@ -0,0 +1,336 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.foo.ArraytypesConfig;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.foo.StructtypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.foo.MaptypesConfig;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.text.Utf8;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigFileFormatterTest {
+
+ private String expected_simpletypes = "stringval \"foo\"\n" +
+ "intval 324234\n" +
+ "longval 324\n" +
+ "doubleval 3.455\n" +
+ "enumval VAL2\n" +
+ "boolval true\n";
+
+ @Test
+ public void require_that_basic_formatting_is_correct() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("stringval", "foo");
+ root.setString("intval", "324234");
+ root.setString("longval", "324");
+ root.setString("doubleval", "3.455");
+ root.setString("enumval", "VAL2");
+ root.setString("boolval", "true");
+
+ assertConfigFormat(slime, expected_simpletypes);
+ }
+
+ @Test
+ public void require_that_basic_formatting_is_correct_with_types() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("stringval", "foo");
+ root.setLong("intval", 324234);
+ root.setLong("longval", 324);
+ root.setDouble("doubleval", 3.455);
+ root.setString("enumval", "VAL2");
+ root.setBool("boolval", true);
+
+ assertConfigFormat(slime, expected_simpletypes);
+ }
+
+ private void assertConfigFormat(Slime slime, String expected_simpletypes) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(expected_simpletypes));
+ }
+
+ @Test
+ public void require_that_field_not_found_is_ignored() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("nosuchfield", "bar");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(""));
+ }
+
+ // TODO: Reenable this when we can reenable typechecking.
+ @Ignore
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_illegal_int_throws_exception() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("intval", "invalid");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(new ByteArrayOutputStream(), slime);
+ }
+
+ // TODO: Reenable this when we can reenable typechecking.
+ @Ignore
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_illegal_long_throws_exception() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("longval", "invalid");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(new ByteArrayOutputStream(), slime);
+ }
+
+ // TODO: Reenable this when we can reenable typechecking.
+ @Ignore
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_illegal_double_throws_exception() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("doubleval", "invalid");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(new ByteArrayOutputStream(), slime);
+ }
+
+ @Test
+ public void require_that_illegal_boolean_becomes_false() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("boolval", "invalid");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is("boolval false\n"));
+ }
+
+ // TODO: Remove this when we can reenable typechecking.
+ @Test
+ public void require_that_types_are_not_checked() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("enumval", "null");
+ root.setString("intval", "null");
+ root.setString("longval", "null");
+ root.setString("boolval", "null");
+ root.setString("doubleval", "null");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString("UTF-8"), is("enumval null\nintval null\nlongval null\nboolval false\ndoubleval null\n"));
+ }
+
+ // TODO: Reenable this when we can reenable typechecking.
+ @Ignore
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_illegal_enum_throws_exception() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("enumval", "invalid");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(new ByteArrayOutputStream(), slime);
+ }
+
+ @Test
+ public void require_that_strings_are_encoded() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ String value = "\u7d22";
+ root.setString("stringval", value);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString("UTF-8"), is("stringval \"" + value + "\"\n"));
+ }
+
+ @Test
+ public void require_that_array_formatting_is_correct() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ Cursor boolarr = root.setArray("boolarr");
+ boolarr.addString("true");
+ boolarr.addString("false");
+ Cursor doublearr = root.setArray("doublearr");
+ doublearr.addString("3.14");
+ doublearr.addString("1.414");
+ Cursor enumarr = root.setArray("enumarr");
+ enumarr.addString("VAL1");
+ enumarr.addString("VAL2");
+ Cursor intarr = root.setArray("intarr");
+ intarr.addString("3");
+ intarr.addString("5");
+ Cursor longarr = root.setArray("longarr");
+ longarr.addString("55");
+ longarr.addString("66");
+ Cursor stringarr = root.setArray("stringarr");
+ stringarr.addString("foo");
+ stringarr.addString("bar");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("arraytypes", new StringReader(StringUtilities.implode(ArraytypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(
+ "boolarr[0] true\n" +
+ "boolarr[1] false\n" +
+ "doublearr[0] 3.14\n" +
+ "doublearr[1] 1.414\n" +
+ "enumarr[0] VAL1\n" +
+ "enumarr[1] VAL2\n" +
+ "intarr[0] 3\n" +
+ "intarr[1] 5\n" +
+ "longarr[0] 55\n" +
+ "longarr[1] 66\n" +
+ "stringarr[0] \"foo\"\n" +
+ "stringarr[1] \"bar\"\n"));
+ }
+
+ @Test
+ public void require_that_map_formatting_is_correct() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ Cursor boolval = root.setObject("boolmap");
+ boolval.setString("foo", "true");
+ boolval.setString("bar", "false");
+ root.setObject("intmap").setString("foo", "1234");
+ root.setObject("longmap").setString("foo", "12345");
+ root.setObject("doublemap").setString("foo", "3.14");
+ root.setObject("stringmap").setString("foo", "bar");
+ root.setObject("innermap").setObject("bar").setString("foo", "1234");
+ root.setObject("nestedmap").setObject("baz").setObject("inner").setString("foo", "1234");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("maptypes", new StringReader(StringUtilities.implode(MaptypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(
+ "boolmap{\"foo\"} true\n" +
+ "boolmap{\"bar\"} false\n" +
+ "intmap{\"foo\"} 1234\n" +
+ "longmap{\"foo\"} 12345\n" +
+ "doublemap{\"foo\"} 3.14\n" +
+ "stringmap{\"foo\"} \"bar\"\n" +
+ "innermap{\"bar\"}.foo 1234\n" +
+ "nestedmap{\"baz\"}.inner{\"foo\"} 1234\n"));
+ }
+
+ @Test
+ public void require_that_struct_formatting_is_correct() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ Cursor simple = root.setObject("simple");
+ simple.setString("name", "myname");
+ simple.setString("gender", "FEMALE");
+ Cursor array = simple.setArray("emails");
+ array.addString("foo@bar.com");
+ array.addString("bar@baz.net");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("structtypes", new StringReader(StringUtilities.implode(StructtypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(
+ "simple.name \"myname\"\n" +
+ "simple.gender FEMALE\n" +
+ "simple.emails[0] \"foo@bar.com\"\n" +
+ "simple.emails[1] \"bar@baz.net\"\n"
+ ));
+ }
+
+ @Test
+ public void require_that_complex_struct_formatting_is_correct() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+
+ Cursor nested = root.setObject("nested");
+ Cursor nested_inner = nested.setObject("inner");
+ nested_inner.setString("name", "baz");
+ nested_inner.setString("gender", "FEMALE");
+ Cursor nested_inner_arr = nested_inner.setArray("emails");
+ nested_inner_arr.addString("foo");
+ nested_inner_arr.addString("bar");
+
+ Cursor nestedarr = root.setArray("nestedarr");
+ Cursor nestedarr1 = nestedarr.addObject();
+ Cursor inner1 = nestedarr1.setObject("inner");
+ inner1.setString("name", "foo");
+ inner1.setString("gender", "FEMALE");
+ Cursor inner1arr = inner1.setArray("emails");
+ inner1arr.addString("foo@bar");
+ inner1arr.addString("bar@foo");
+
+ Cursor complexarr = root.setArray("complexarr");
+ Cursor complexarr1 = complexarr.addObject();
+ Cursor innerarr1 = complexarr1.setArray("innerarr");
+ Cursor innerarr11 = innerarr1.addObject();
+ innerarr11.setString("name", "bar");
+ innerarr11.setString("gender", "MALE");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InnerCNode def = new DefParser("structtypes", new StringReader(StringUtilities.implode(StructtypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is(
+ "nested.inner.name \"baz\"\n" +
+ "nested.inner.gender FEMALE\n" +
+ "nested.inner.emails[0] \"foo\"\n" +
+ "nested.inner.emails[1] \"bar\"\n" +
+ "nestedarr[0].inner.name \"foo\"\n" +
+ "nestedarr[0].inner.gender FEMALE\n" +
+ "nestedarr[0].inner.emails[0] \"foo@bar\"\n" +
+ "nestedarr[0].inner.emails[1] \"bar@foo\"\n" +
+ "complexarr[0].innerarr[0].name \"bar\"\n" +
+ "complexarr[0].innerarr[0].gender MALE\n"
+ ));
+ }
+
+ @Test
+ public void require_that_strings_are_properly_escaped() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("stringval", "some\"quotes\\\"instring");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ConfigFileFormat(def).encode(baos, slime);
+ assertThat(baos.toString(), is("stringval \"some\\\"quotes\\\\\\\"instring\"\n"));
+ }
+
+ @Test
+ @Ignore
+ public void require_that_utf8_works() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ final String input = "Hei \u00E6\u00F8\u00E5 \n \uBC14\uB451 \u00C6\u00D8\u00C5 hallo";
+ root.setString("stringval", input);
+ System.out.println(bytesToHexString(Utf8.toBytes(input)));
+ InnerCNode def = new DefParser("simpletypes", new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n"))).getTree();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ConfigFileFormat(def).encode(baos, slime);
+ System.out.println(bytesToHexString(baos.toByteArray()));
+ assertThat(Utf8.toString(baos.toByteArray()), is("stringval \"" + input + "\"\n"));
+ }
+
+ public static String bytesToHexString(byte[] bytes){
+ StringBuilder sb = new StringBuilder();
+ for(byte b : bytes){
+ sb.append(String.format("%02x", b&0xff));
+ }
+ return sb.toString();
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java
new file mode 100644
index 00000000000..ce625727491
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigHelperTest.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.subscription.ConfigSourceSet;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1.9
+ */
+public class ConfigHelperTest {
+
+ @Test
+ public void basic() {
+ ConfigSourceSet configSourceSet = new ConfigSourceSet("host.com");
+ ConfigHelper helper = new ConfigHelper(configSourceSet);
+ assertThat(helper.getConfigSourceSet(), is(configSourceSet));
+ assertThat(helper.getConnectionPool().getAllSourceAddresses(), is("host.com"));
+ assertThat(helper.getTimingValues().getSubscribeTimeout(), is(new TimingValues().getSubscribeTimeout()));
+
+ // Specify timing values
+ TimingValues tv = new TimingValues();
+ tv.setSubscribeTimeout(11L);
+ helper = new ConfigHelper(configSourceSet, tv);
+ assertThat(helper.getTimingValues().getSubscribeTimeout(), is(tv.getSubscribeTimeout()));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java
new file mode 100644
index 00000000000..e824571d9d1
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigKeyTest.java
@@ -0,0 +1,121 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.yahoo.foo.AppConfig;
+import com.yahoo.config.ConfigurationRuntimeException;
+import com.yahoo.config.codegen.CNode;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigKeyTest {
+
+ @Test
+ public void testConfigId() {
+ String namespace = "bar";
+ ConfigKey<?> key1 = new ConfigKey<>("foo", "a/b/c", namespace);
+ ConfigKey<?> key2 = new ConfigKey<>("foo", "a/b/c", namespace);
+
+ assertEquals(key1, key2);
+
+ ConfigKey<?> key3 = new ConfigKey<>("foo", "a/b/c/d", namespace);
+ assertTrue(!key1.equals(key3));
+ assertFalse(key1.equals(key3));
+
+ assertEquals("a/b/c", new ConfigKey<>("foo", "a/b/c", namespace).getConfigId());
+ assertEquals("a", new ConfigKey<>("foo", "a", namespace).getConfigId());
+ assertEquals("", new ConfigKey<>("foo", "", namespace).getConfigId());
+
+ assertTrue(key1.equals(key1));
+ assertFalse(key1.equals(key3));
+ assertFalse(key1.equals(new Object()));
+
+ ConfigKey<?> key4 = new ConfigKey<>("myConfig", null, namespace);
+ assertEquals("", key4.getConfigId());
+ }
+
+ @Test
+ public void testConfigKey() {
+ String name = AppConfig.CONFIG_DEF_NAME;
+ String namespace = AppConfig.CONFIG_DEF_NAMESPACE;
+ String md5 = AppConfig.CONFIG_DEF_MD5;
+ String configId = "myId";
+
+ ConfigKey<AppConfig> classKey = new ConfigKey<>(AppConfig.class, configId);
+ assertEquals("Name is set correctly from class", name, classKey.getName());
+ assertEquals("Namespace is set correctly from class", namespace, classKey.getNamespace());
+ assertEquals(configId, classKey.getConfigId());
+ assertEquals("Md5 is set correctly from class", md5, classKey.getMd5());
+
+ ConfigKey<?> stringKey = new ConfigKey<>(name, configId, namespace);
+ assertEquals("Key created from class equals key created from strings", stringKey, classKey);
+ }
+
+ @Test(expected = ConfigurationRuntimeException.class)
+ public void testNoName() {
+ new ConfigKey<>(null, "", "");
+ }
+
+ // Tests namespace and equals with combinations of namespace.
+ @Test
+ public void testNamespace() {
+ ConfigKey<?> noNamespace = new ConfigKey<>("name", "id", null);
+ ConfigKey<?> namespaceFoo = new ConfigKey<>("name", "id", "foo");
+ ConfigKey<?> namespaceBar = new ConfigKey<>("name", "id", "bar");
+ assertTrue(noNamespace.equals(noNamespace));
+ assertTrue(namespaceFoo.equals(namespaceFoo));
+ assertFalse(noNamespace.equals(namespaceFoo));
+ assertFalse(namespaceFoo.equals(noNamespace));
+ assertFalse(namespaceFoo.equals(namespaceBar));
+ assertEquals(noNamespace.getNamespace(), CNode.DEFAULT_NAMESPACE);
+ assertEquals(namespaceBar.getNamespace(), "bar");
+ }
+
+ @Test
+ public void testSorting() {
+ ConfigKey<?> k1 = new ConfigKey<>("name3", "id2", "nsc");
+ ConfigKey<?> k2 = new ConfigKey<>("name2", "id2", "nsb");
+ ConfigKey<?> k3 = new ConfigKey<>("name1", "id2", "nsa");
+ List<ConfigKey<?>> keys = new ArrayList<>();
+ keys.add(k2);
+ keys.add(k1);
+ keys.add(k3);
+ Collections.sort(keys);
+ assertEquals(keys.get(0), k3);
+ assertEquals(keys.get(1), k2);
+ assertEquals(keys.get(2), k1);
+
+ k1 = new ConfigKey<>("name2", "id2", "nsa");
+ k2 = new ConfigKey<>("name3", "id3", "nsa");
+ k3 = new ConfigKey<>("name1", "id4", "nsa");
+ keys = new ArrayList<>();
+ keys.add(k3);
+ keys.add(k2);
+ keys.add(k1);
+ Collections.sort(keys);
+ assertEquals(keys.get(0), k3);
+ assertEquals(keys.get(1), k1);
+ assertEquals(keys.get(2), k2);
+
+ k1 = new ConfigKey<>("name", "idC", "nsa");
+ k2 = new ConfigKey<>("name", "idA", "nsa");
+ k3 = new ConfigKey<>("name", "idB", "nsa");
+ keys = new ArrayList<>();
+ keys.add(k1);
+ keys.add(k2);
+ keys.add(k3);
+ Collections.sort(keys);
+ assertEquals(keys.get(0), k2);
+ assertEquals(keys.get(1), k3);
+ assertEquals(keys.get(2), k1);
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadBuilderTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadBuilderTest.java
new file mode 100644
index 00000000000..24ade0f83c2
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadBuilderTest.java
@@ -0,0 +1,363 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigPayloadBuilderTest {
+
+ private ConfigPayloadBuilder builderWithDef;
+
+ private Cursor createSlime(ConfigPayloadBuilder builder) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ builder.resolve(root);
+ return root;
+ }
+
+ @Before
+ public void setupBuilder() {
+ ConfigDefinition def = new ConfigDefinition("foo", "1", "bar");
+ def.addBoolDef("boolval");
+ ConfigDefinition mystruct = def.structDef("mystruct");
+ mystruct.addIntDef("foofield");
+ def.arrayDef("myarray").setTypeSpec(new ConfigDefinition.TypeSpec("myarray", "int", null, null, null, null));
+ ConfigDefinition myinnerarray = def.innerArrayDef("myinnerarray");
+ myinnerarray.addIntDef("foo");
+ builderWithDef = new ConfigPayloadBuilder(def, new ArrayList<String>());
+ }
+
+ @Test
+ public void require_that_simple_fields_are_set() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ builder.setField("foo", "bar");
+ builder.setField("bar", "barz");
+ builder.getObject("bar").setValue("baz");
+ Cursor root = createSlime(builder);
+ assertEquals("bar", root.field("foo").asString());
+ assertEquals("baz", root.field("bar").asString());
+ }
+
+ @Test
+ public void require_that_simple_fields_can_be_overwritten() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ builder.setField("foo", "bar");
+ builder.setField("foo", "baz");
+ Cursor root = createSlime(builder);
+ // XXX: Not sure if this is the _right_ behavior.
+ assertEquals("baz", root.field("foo").asString());
+ }
+
+ @Test
+ public void require_that_struct_values_are_created() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder struct = builder.getObject("foo");
+ struct.setField("bar", "baz");
+
+ Cursor root = createSlime(builder);
+ Cursor s = root.field("foo");
+ assertEquals("baz", s.field("bar").asString());
+ }
+
+
+ @Test
+ public void require_that_maps_are_created() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.MapBuilder map = builder.getMap("foo");
+ assertNotNull(map);
+ }
+
+ @Test
+ public void require_that_maps_support_simple_values() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.MapBuilder map = builder.getMap("foo");
+ map.put("fookey", "foovalue");
+ map.put("barkey", "barvalue");
+ map.put("bazkey", "bazvalue");
+ map.put("fookey", "lolvalue");
+ assertThat(map.getElements().size(), is(3));
+ Cursor root = createSlime(builder);
+ Cursor a = root.field("foo");
+ assertThat(a.field("barkey").asString(), is("barvalue"));
+ assertThat(a.field("bazkey").asString(), is("bazvalue"));
+ assertThat(a.field("fookey").asString(), is("lolvalue"));
+ }
+
+ @Test
+ public void require_that_arrays_are_created() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ assertNotNull(array);
+ }
+
+ @Test
+ public void require_that_arrays_can_be_appended_simple_values() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ array.append("bar");
+ array.append("baz");
+ array.append("bim");
+ assertThat(array.getElements().size(), is(3));
+ Cursor root = createSlime(builder);
+ Cursor a = root.field("foo");
+ assertEquals("bar", a.entry(0).asString());
+ assertEquals("baz", a.entry(1).asString());
+ assertEquals("bim", a.entry(2).asString());
+ }
+
+ @Test
+ public void require_that_arrays_can_be_indexed_simple_values() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ array.set(3, "bar");
+ array.set(2, "baz");
+ array.set(6, "bim");
+ array.set(4, "bum");
+
+ Cursor root = createSlime(builder);
+ Cursor a = root.field("foo");
+ assertEquals("bar", a.entry(0).asString());
+ assertEquals("baz", a.entry(1).asString());
+ assertEquals("bim", a.entry(2).asString());
+ assertEquals("bum", a.entry(3).asString());
+ }
+
+ @Test
+ public void require_that_arrays_can_be_appended_structs() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ ConfigPayloadBuilder elem1 = array.append();
+ elem1.setField("bar", "baz");
+ ConfigPayloadBuilder elem2 = array.append();
+ elem2.setField("foo", "bar");
+ Cursor root = createSlime(builder);
+ Cursor a = root.field("foo");
+ assertEquals("baz", a.entry(0).field("bar").asString());
+ assertEquals("bar", a.entry(1).field("foo").asString());
+ }
+
+ @Test
+ public void require_that_arrays_can_be_indexed_structs() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ ConfigPayloadBuilder elem1 = array.set(4);
+ elem1.setField("bar", "baz");
+ ConfigPayloadBuilder elem2 = array.set(2);
+ elem2.setField("foo", "bar");
+
+ Cursor root = createSlime(builder);
+ Cursor a = root.field("foo");
+ assertEquals("baz", a.entry(0).field("bar").asString());
+ assertEquals("bar", a.entry(1).field("foo").asString());
+ }
+
+ @Test
+ public void require_that_get_can_be_used_instead() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ // Causes append to be used
+ ConfigPayloadBuilder b1 = array.get(0);
+ ConfigPayloadBuilder b2 = array.get(1);
+ ConfigPayloadBuilder b3 = array.get(0);
+ ConfigPayloadBuilder b4 = array.get(1);
+ assertThat(b1, is(b3));
+ assertThat(b2, is(b4));
+
+ ConfigPayloadBuilder.Array array_indexed = builder.getArray("bar");
+ ConfigPayloadBuilder bi3 = array_indexed.set(3);
+ ConfigPayloadBuilder bi1 = array_indexed.set(1);
+ ConfigPayloadBuilder bi32 = array_indexed.get(3);
+ ConfigPayloadBuilder bi12 = array_indexed.get(1);
+ assertThat(bi12, is(bi1));
+ assertThat(bi32, is(bi3));
+ }
+
+ @Test
+ public void require_that_builders_can_be_merged() {
+ ConfigPayloadBuilder b1 = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder b2 = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder b3 = new ConfigPayloadBuilder();
+ b1.setField("aaa", "a");
+ b1.getObject("bbb").setField("ccc", "ddd");
+
+ b2.setField("aaa", "b");
+ b2.getObject("bbb").setField("ccc", "eee");
+ b2.getArray("eee").append("kkk");
+ b2.setField("uuu", "ttt");
+
+ b3.setField("aaa", "c");
+ b3.getObject("bbb").setField("ccc", "fff");
+ b3.getArray("eee").append("lll");
+ b3.setField("uuu", "vvv");
+
+ assertThat(b1.override(b2), is(b1));
+ assertThat(b1.override(b3), is(b1));
+
+ Cursor b1root = createSlime(b1);
+ Cursor b2root = createSlime(b2);
+ Cursor b3root = createSlime(b3);
+ assertThat(b3root.field("aaa").asString(), is("c"));
+ assertThat(b3root.field("bbb").field("ccc").asString(), is("fff"));
+ assertThat(b3root.field("eee").children(), is(1));
+ assertThat(b3root.field("eee").entry(0).asString(), is("lll"));
+ assertThat(b3root.field("uuu").asString(), is("vvv"));
+
+ assertThat(b2root.field("aaa").asString(), is("b"));
+ assertThat(b2root.field("bbb").field("ccc").asString(), is("eee"));
+ assertThat(b2root.field("eee").children(), is(1));
+ assertThat(b2root.field("eee").entry(0).asString(), is("kkk"));
+ assertThat(b2root.field("uuu").asString(), is("ttt"));
+
+ assertThat(b1root.field("aaa").asString(), is("c"));
+ assertThat(b1root.field("bbb").field("ccc").asString(), is("fff"));
+ assertThat(b1root.field("eee").children(), is(2));
+ assertThat(b1root.field("eee").entry(0).asString(), is("kkk"));
+ assertThat(b1root.field("eee").entry(1).asString(), is("lll"));
+ assertThat(b1root.field("uuu").asString(), is("vvv"));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void require_that_append_conflicts_with_index() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ array.set(0, "bar");
+ array.append("baz");
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void require_that_index_conflicts_with_append() {
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder();
+ ConfigPayloadBuilder.Array array = builder.getArray("foo");
+ array.append("baz");
+ array.set(0, "bar");
+ }
+
+ @Test
+ public void require_that_builder_can_be_created_from_payload() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("foo", "bar");
+ Cursor obj = root.setObject("foorio");
+ obj.setString("bar", "bam");
+ Cursor obj2 = obj.setObject("bario");
+ obj2.setString("bim", "bul");
+ Cursor a2 = obj.setArray("blim");
+ Cursor arrayobj = a2.addObject();
+ arrayobj.setString("fim", "fam");
+ Cursor arrayobj2 = a2.addObject();
+ arrayobj2.setString("blim", "blam");
+ Cursor a1 = root.setArray("arrio");
+ a1.addString("himbio");
+
+ ConfigPayloadBuilder builder = new ConfigPayloadBuilder(new ConfigPayload(slime));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ConfigPayload.fromBuilder(builder).serialize(baos, new JsonFormat(true));
+ assertThat(baos.toString(), is("{\"foo\":\"bar\",\"foorio\":{\"bar\":\"bam\",\"bario\":{\"bim\":\"bul\"},\"blim\":[{\"fim\":\"fam\"},{\"blim\":\"blam\"}]},\"arrio\":[\"himbio\"]}"));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_values_are_verified_against_def() {
+ builderWithDef.setField("boolval", "true");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ builderWithDef.setField("boolval", "invalid");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_arrays_must_exist() {
+ builderWithDef.getArray("arraydoesnotexist");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_structs_must_exist() {
+ builderWithDef.getObject("structdoesnotexist");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_definition_is_passed_to_childstruct() {
+ ConfigPayloadBuilder nestedStruct = builderWithDef.getObject("mystruct");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ nestedStruct.setField("doesnotexit", "foo");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_definition_is_passed_to_childstruct_but_invalid_field_will_throw() {
+ ConfigPayloadBuilder nestedStruct = builderWithDef.getObject("mystruct");
+ nestedStruct.setField("foofield", "invalid");
+ //assertThat(builderWithDef.warnings().size(), is(2));
+ }
+
+ @Test
+ public void require_that_definition_is_passed_to_childarray() {
+ ConfigPayloadBuilder.Array nestedArray = builderWithDef.getArray("myarray");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ nestedArray.append("1337");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_definition_is_passed_to_childarray_but_invalid_field_will_throw() {
+ ConfigPayloadBuilder.Array nestedArray = builderWithDef.getArray("myarray");
+ nestedArray.append("invalid");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test
+ public void require_that_definition_is_passed_to_inner_array_with_append() {
+ ConfigPayloadBuilder.Array innerArray = builderWithDef.getArray("myinnerarray");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ ConfigPayloadBuilder innerStruct = innerArray.append();
+ assertNotNull(innerStruct.getConfigDefinition());
+ assertThat(builderWithDef.warnings().size(), is(0));
+ innerStruct.setField("foo", "1337");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_definition_is_passed_to_inner_array_with_append_but_invalid_field_will_throw() {
+ ConfigPayloadBuilder.Array innerArray = builderWithDef.getArray("myinnerarray");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ ConfigPayloadBuilder innerStruct = innerArray.append();
+ innerStruct.setField("foo", "invalid");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+
+ @Test
+ public void require_that_definition_is_passed_to_inner_array_with_index() {
+ ConfigPayloadBuilder.Array innerArray = builderWithDef.getArray("myinnerarray");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ ConfigPayloadBuilder innerStruct = innerArray.set(1);
+ assertNotNull(innerStruct.getConfigDefinition());
+ assertThat(builderWithDef.warnings().size(), is(0));
+ innerStruct.setField("foo", "1337");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void require_that_definition_is_passed_to_inner_array_with_index_but_invalid_field_will_throw() {
+ ConfigPayloadBuilder.Array innerArray = builderWithDef.getArray("myinnerarray");
+ assertThat(builderWithDef.warnings().size(), is(0));
+ ConfigPayloadBuilder innerStruct = innerArray.set(1);
+ innerStruct.setField("foo", "invalid");
+ //assertThat(builderWithDef.warnings().size(), is(1));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java
new file mode 100644
index 00000000000..1e7b66fac38
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ConfigPayloadTest.java
@@ -0,0 +1,461 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.foo.*;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.text.StringUtilities;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf 3
+ * @since 5.1
+ */
+public class ConfigPayloadTest {
+
+ @Test
+ public void test_simple_builder() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("stringval", "abcde");
+ assertThat(config.stringval(), is("abcde"));
+ }
+
+ @Test
+ public void require_that_arrays_are_built() throws Exception {
+ AppConfig config = createAppConfig("foo", "4", new String[] { "bar", "baz", "bim" });
+ assertThat(config.message(), is("foo"));
+ assertThat(config.times(), is(4));
+ assertThat(config.a(0).name(), is("bar"));
+ assertThat(config.a(1).name(), is("baz"));
+ assertThat(config.a(2).name(), is("bim"));
+ }
+
+ @Test
+ public void test_int_leaf_legal() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("intval", "0");
+ assertThat(config.intval(), is(0));
+ config = createSimpletypesConfig("intval", String.valueOf(Integer.MIN_VALUE));
+ assertThat(config.intval(), is(Integer.MIN_VALUE));
+ config = createSimpletypesConfig("intval", String.valueOf(Integer.MAX_VALUE));
+ assertThat(config.intval(), is(Integer.MAX_VALUE));
+ config = createSimpletypesConfig("intval", String.valueOf(10));
+ assertThat(config.intval(), is(10));
+ config = createSimpletypesConfig("intval", String.valueOf(-10));
+ assertThat(config.intval(), is(-10));
+ }
+
+ @Test (expected = RuntimeException.class)
+ public void test_int_leaf_too_large() throws Exception {
+ createSimpletypesConfig("intval", String.valueOf(Integer.MAX_VALUE) + "00");
+ }
+
+ @Test (expected = RuntimeException.class)
+ public void test_int_leaf_too_large_neg() throws Exception {
+ createSimpletypesConfig("intval", String.valueOf(Integer.MIN_VALUE) + "00");
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void test_int_leaf_illegal_string() throws Exception {
+ createSimpletypesConfig("intval", "illegal");
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void test_int_leaf_illegal_string_suffix() throws Exception {
+ createSimpletypesConfig("intval", "123illegal");
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void test_int_leaf_illegal_string_prefix() throws Exception {
+ createSimpletypesConfig("intval", "illegal123");
+ }
+
+ @Test
+ public void test_that_empty_is_empty() {
+ ConfigPayload payload = ConfigPayload.empty();
+ assertTrue(payload.isEmpty());
+ payload = ConfigPayload.fromString("{\"foo\":4}");
+ assertFalse(payload.isEmpty());
+ }
+
+
+ @Test
+ public void test_long_leaf() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("longval", "0");
+ assertThat(config.longval(), is(0l));
+ config = createSimpletypesConfig("longval", String.valueOf(Long.MIN_VALUE));
+ assertThat(config.longval(), is(Long.MIN_VALUE));
+ config = createSimpletypesConfig("longval", String.valueOf(Long.MAX_VALUE));
+ assertThat(config.longval(), is(Long.MAX_VALUE));
+ config = createSimpletypesConfig("longval", String.valueOf(10));
+ assertThat(config.longval(), is(10l));
+ config = createSimpletypesConfig("longval", String.valueOf(-10));
+ assertThat(config.longval(), is(-10l));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void test_long_leaf_illegal_string() throws Exception {
+ createSimpletypesConfig("longval", "illegal");
+ }
+
+ @Test (expected = RuntimeException.class)
+ public void test_long_leaf_too_large() throws Exception {
+ createSimpletypesConfig("longval", String.valueOf(Long.MAX_VALUE) + "00");
+ }
+
+ @Test (expected = RuntimeException.class)
+ public void test_long_leaf_too_large_neg() throws Exception {
+ createSimpletypesConfig("longval", String.valueOf(Long.MIN_VALUE) + "00");
+ }
+
+ @Test
+ public void test_double_leaf() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("doubleval", "0");
+ assertEquals(0.0, config.doubleval(), 0.01);
+ assertEquals(133.3, createSimpletypesConfig("doubleval", "133.3").doubleval(), 0.001);
+ config = createSimpletypesConfig("doubleval", String.valueOf(Double.MIN_VALUE));
+ assertEquals(Double.MIN_VALUE, config.doubleval(), 0.0000001);
+ config = createSimpletypesConfig("doubleval", String.valueOf(Double.MAX_VALUE));
+ assertEquals(Double.MAX_VALUE, config.doubleval(), 0.0000001);
+ }
+
+ @Test
+ public void test_serializer() throws IOException {
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ assertThat(payload.toString(true), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void test_double_leaf_illegal_string() throws Exception {
+ createSimpletypesConfig("doubleval", "illegal");
+ }
+
+ @Test
+ public void test_double_leaf_negative_infinity() throws Exception {
+ assertThat(createSimpletypesConfig("doubleval", "-Infinity").doubleval(), is(Double.NEGATIVE_INFINITY));
+ assertThat(createSimpletypesConfig("doubleval", "Infinity").doubleval(), is(Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void test_enum_leaf() throws Exception {
+ assertThat(createSimpletypesConfig("enumval", "VAL1").enumval(), is(SimpletypesConfig.Enumval.Enum.VAL1));
+ assertThat(createSimpletypesConfig("enumval", "VAL2").enumval(), is(SimpletypesConfig.Enumval.Enum.VAL2));
+ }
+
+ @Test(expected=RuntimeException.class)
+ public void test_enum_leaf_illegal_string() throws Exception {
+ createSimpletypesConfig("enumval", "ILLEGAL");
+ }
+
+ @Test
+ public void test_bool_leaf() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("boolval", "true");
+ assertThat(config.boolval(), is(true));
+ config = createSimpletypesConfig("boolval", "false");
+ assertThat(config.boolval(), is(false));
+ config = createSimpletypesConfig("boolval", "TRUE");
+ assertThat(config.boolval(), is(true));
+ config = createSimpletypesConfig("boolval", "FALSE");
+ assertThat(config.boolval(), is(false));
+ }
+
+ @Test// FIXME: (expected = RuntimeException.class)
+ public void test_bool_leaf_illegal() throws Exception {
+ createSimpletypesConfig("boolval", "illegal");
+ }
+
+ @Test
+ public void test_string_illegal_value() throws Exception {
+ // TODO: What do we consider illegal string values?
+ createSimpletypesConfig("stringval", "insert_illegal_value_please");
+ }
+
+ @Test
+ public void test_int_array() throws Exception {
+ // Normal behavior
+ ArraytypesConfig config = createArraytypesConfig("intarr", new String[] { "2", "3", "1", "-2", "5"});
+ assertThat(config.intarr().size(), is(5));
+ assertThat(config.intarr(0), is(2));
+ assertThat(config.intarr(1), is(3));
+ assertThat(config.intarr(2), is(1));
+ assertThat(config.intarr(3), is(-2));
+ assertThat(config.intarr(4), is(5));
+
+ final int size = 100;
+ String [] largeArray = new String[size];
+ for (int i = 0; i < size; i++) {
+ int value = (int)(Math.random() * Integer.MAX_VALUE);
+ largeArray[i] = String.valueOf(value);
+ }
+ config = createArraytypesConfig("intarr", largeArray);
+ assertThat(config.intarr().size(), is(largeArray.length));
+ for (int i = 0; i < size; i++) {
+ assertThat(config.intarr(i), is(Integer.valueOf(largeArray[i])));
+ }
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void test_int_array_illegal() throws Exception {
+ createArraytypesConfig("intarr", new String[] { "2", "3", "illegal", "-2", "5"});
+ }
+
+ @Test
+ public void test_long_array() throws Exception {
+ // Normal behavior
+ ArraytypesConfig config = createArraytypesConfig("longarr", new String[] { "2", "3", "1", "-2", "5"});
+ assertThat(config.longarr().size(), is(5));
+ assertThat(config.longarr(0), is(2l));
+ assertThat(config.longarr(1), is(3l));
+ assertThat(config.longarr(2), is(1l));
+ assertThat(config.longarr(3), is(-2l));
+ assertThat(config.longarr(4), is(5l));
+
+ final int size = 100;
+ String [] largeArray = new String[size];
+ for (int i = 0; i < size; i++) {
+ long value = (long) (Math.random() * Long.MAX_VALUE);
+ largeArray[i] = String.valueOf(value);
+ }
+ config = createArraytypesConfig("longarr", largeArray);
+ assertThat(config.longarr().size(), is(largeArray.length));
+ for (int i = 0; i < size; i++) {
+ assertThat(config.longarr(i), is(Long.valueOf(largeArray[i])));
+ }
+ }
+
+ @Test
+ public void test_double_array() throws Exception {
+ // Normal behavior
+ ArraytypesConfig config = createArraytypesConfig("doublearr", new String[] { "2.1", "3.3", "1.5", "-2.1", "Infinity"});
+ assertThat(config.doublearr().size(), is(5));
+ assertEquals(2.1, config.doublearr(0), 0.01);
+ assertEquals(3.3, config.doublearr(1), 0.01);
+ assertEquals(1.5, config.doublearr(2), 0.01);
+ assertEquals(-2.1, config.doublearr(3), 0.01);
+ assertEquals(Double.POSITIVE_INFINITY, config.doublearr(4), 0.01);
+ }
+
+ @Test
+ public void test_enum_array() throws Exception {
+ // Normal behavior
+ ArraytypesConfig config = createArraytypesConfig("enumarr", new String[] { "VAL1", "VAL2", "VAL1" });
+ assertThat(config.enumarr().size(), is(3));
+ assertThat(config.enumarr(0), is(ArraytypesConfig.Enumarr.Enum.VAL1));
+ assertThat(config.enumarr(1), is(ArraytypesConfig.Enumarr.Enum.VAL2));
+ assertThat(config.enumarr(2), is(ArraytypesConfig.Enumarr.Enum.VAL1));
+ }
+
+ @Test
+ public void test_simple_struct() throws Exception {
+ StructtypesConfig config = createStructtypesConfigSimple("foobar", "MALE", new String[] { "foo@bar", "bar@foo" });
+ assertThat(config.simple().name(), is("foobar"));
+ assertThat(config.simple().gender(), is(StructtypesConfig.Simple.Gender.Enum.MALE));
+ assertThat(config.simple().emails(0), is("foo@bar"));
+ assertThat(config.simple().emails(1), is("bar@foo"));
+ }
+
+
+ @Test
+ public void test_simple_struct_arrays() throws Exception {
+ StructtypesConfig config = createStructtypesConfigArray(new String[] { "foo", "bar" },
+ new String[] { "MALE", "FEMALE" });
+ assertThat(config.simplearr(0).name(), is("foo"));
+ assertThat(config.simplearr(0).gender(), is(StructtypesConfig.Simplearr.Gender.MALE));
+ assertThat(config.simplearr(1).name(), is("bar"));
+ assertThat(config.simplearr(1).gender(), is(StructtypesConfig.Simplearr.Gender.FEMALE));
+ }
+
+
+ @Test
+ public void test_nested_struct() throws Exception {
+ StructtypesConfig config = createStructtypesConfigNested("foo", "FEMALE");
+ assertThat(config.nested().inner().name(), is("foo"));
+ assertThat(config.nested().inner().gender(), is(StructtypesConfig.Nested.Inner.Gender.Enum.FEMALE));
+ }
+
+
+
+ @Test
+ public void test_nested_struct_array() throws Exception {
+ String [] names = { "foo" ,"bar" };
+ String [] genders = { "FEMALE", "MALE" };
+ String [][] emails = {
+ { "foo@bar" , "bar@foo" },
+ { "bim@bam", "bam@bim" }
+ };
+ StructtypesConfig config = createStructtypesConfigNestedArray(names, genders, emails);
+ assertThat(config.nestedarr(0).inner().name(), is("foo"));
+ assertThat(config.nestedarr(0).inner().gender(), is(StructtypesConfig.Nestedarr.Inner.Gender.FEMALE));
+ assertThat(config.nestedarr(0).inner().emails(0), is("foo@bar"));
+ assertThat(config.nestedarr(0).inner().emails(1), is("bar@foo"));
+
+ assertThat(config.nestedarr(1).inner().name(), is("bar"));
+ assertThat(config.nestedarr(1).inner().gender(), is(StructtypesConfig.Nestedarr.Inner.Gender.MALE));
+ assertThat(config.nestedarr(1).inner().emails(0), is("bim@bam"));
+ assertThat(config.nestedarr(1).inner().emails(1), is("bam@bim"));
+ }
+
+
+ @Test
+ public void test_complex_struct_array() throws Exception {
+ String [][] names = {
+ { "foo", "bar" },
+ { "baz", "bim" }
+ };
+ String [][] genders = {
+ { "FEMALE", "MALE" },
+ { "MALE", "FEMALE" }
+ };
+ StructtypesConfig config = createStructtypesConfigComplexArray(names, genders);
+ assertThat(config.complexarr(0).innerarr(0).name(), is("foo"));
+ assertThat(config.complexarr(0).innerarr(0).gender(), is(StructtypesConfig.Complexarr.Innerarr.Gender.Enum.FEMALE));
+ assertThat(config.complexarr(0).innerarr(1).name(), is("bar"));
+ assertThat(config.complexarr(0).innerarr(1).gender(), is(StructtypesConfig.Complexarr.Innerarr.Gender.Enum.MALE));
+
+ assertThat(config.complexarr(1).innerarr(0).name(), is("baz"));
+ assertThat(config.complexarr(1).innerarr(0).gender(), is(StructtypesConfig.Complexarr.Innerarr.Gender.Enum.MALE));
+ assertThat(config.complexarr(1).innerarr(1).name(), is("bim"));
+ assertThat(config.complexarr(1).innerarr(1).gender(), is(StructtypesConfig.Complexarr.Innerarr.Gender.Enum.FEMALE));
+ }
+
+ @Test
+ public void test_function_test() {
+ // TODO: Test function test config as a complete config example
+ }
+
+ @Test
+ public void test_set_nonexistent_field() throws Exception {
+ createSimpletypesConfig("doesnotexist", "blabla");
+ }
+
+ @Test
+ public void test_escaped_string() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("stringval", "b=\"escaped\"");
+ assertThat(config.stringval(), is("b=\"escaped\""));
+ }
+
+ @Test
+ public void test_unicode() throws Exception {
+ SimpletypesConfig config = createSimpletypesConfig("stringval", "Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo");
+ assertThat(config.stringval(), is("Hei \u00E6\u00F8\u00E5 \uBC14\uB451 \u00C6\u00D8\u00C5 hallo"));
+ }
+
+ @Test
+ public void test_empty_payload() throws Exception {
+ Slime slime = new Slime();
+ slime.setObject();
+ IntConfig config = new ConfigPayload(slime).toInstance(IntConfig.class, "");
+ assertThat(config.intVal(), is(1));
+ }
+
+ @Test
+ public void test_applying_extra_default_values() {
+ InnerCNode clientDef = new DefParser(SimpletypesConfig.CONFIG_DEF_NAME, new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n") + "\nnewfield int default=3\n")).getTree();
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ payload = payload.applyDefaultsFromDef(clientDef);
+ assertThat(payload.toString(true), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\",\"newfield\":\"3\"}"));
+ }
+
+ /**
+ * TODO: Test invalid slime trees?
+ * TODO: Test sending in wrong class
+ */
+
+ /**********************************************************************************************
+ * Helper methods. consider moving out to another class for reuse by merge tester. *
+ **********************************************************************************************/
+ private AppConfig createAppConfig(String message, String times, String [] names) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("message", message);
+ root.setString("times", times);
+ Cursor arr = root.setArray("a");
+ for (String name : names) {
+ Cursor obj = arr.addObject();
+ obj.setString("name", name);
+ }
+ return new ConfigPayload(slime).toInstance(AppConfig.class, "");
+ }
+
+ private SimpletypesConfig createSimpletypesConfig(String field, String value) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString(field, value);
+ return new ConfigPayload(slime).toInstance(SimpletypesConfig.class, "");
+ }
+
+ private ArraytypesConfig createArraytypesConfig(String field, String [] values) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ Cursor array = root.setArray(field);
+ for (String value : values) {
+ array.addString(value);
+ }
+ return new ConfigPayload(slime).toInstance(ArraytypesConfig.class, "");
+ }
+
+
+ private void addStructFields(Cursor struct, String name, String gender, String [] emails) {
+ struct.setString("name", name);
+ struct.setString("gender", gender);
+ if (emails != null) {
+ Cursor array = struct.setArray("emails");
+ for (String email : emails) {
+ array.addString(email);
+ }
+ }
+ }
+
+ private StructtypesConfig createStructtypesConfigSimple(String name, String gender, String [] emails) {
+ Slime slime = new Slime();
+ addStructFields(slime.setObject().setObject("simple"), name, gender, emails);
+ return new ConfigPayload(slime).toInstance(StructtypesConfig.class, "");
+ }
+
+ private StructtypesConfig createStructtypesConfigArray(String[] names, String[] genders) {
+ Slime slime = new Slime();
+ Cursor array = slime.setObject().setArray("simplearr");
+ assertEquals(names.length, genders.length);
+ for (int i = 0; i < names.length; i++) {
+ addStructFields(array.addObject(), names[i], genders[i], null);
+ }
+ return new ConfigPayload(slime).toInstance(StructtypesConfig.class, "");
+ }
+
+ private StructtypesConfig createStructtypesConfigNested(String name, String gender) {
+ Slime slime = new Slime();
+ addStructFields(slime.setObject().setObject("nested").setObject("inner"), name, gender, null);
+ return new ConfigPayload(slime).toInstance(StructtypesConfig.class, "");
+ }
+
+ private StructtypesConfig createStructtypesConfigNestedArray(String[] names, String [] genders, String [][] emails) {
+ Slime slime = new Slime();
+ Cursor array = slime.setObject().setArray("nestedarr");
+ assertEquals(names.length, genders.length);
+ for (int i = 0; i < names.length; i++) {
+ addStructFields(array.addObject().setObject("inner"), names[i], genders[i], emails[i]);
+ }
+ return new ConfigPayload(slime).toInstance(StructtypesConfig.class, "");
+ }
+
+ private StructtypesConfig createStructtypesConfigComplexArray(String [][] names, String [][] genders) {
+ Slime slime = new Slime();
+ Cursor array = slime.setObject().setArray("complexarr");
+ assertEquals(names.length, genders.length);
+ for (int i = 0; i < names.length; i++) {
+ assertEquals(names[i].length, genders[i].length);
+
+ Cursor innerarr = array.addObject().setArray("innerarr");
+ for (int k = 0; k < names[i].length; k++) {
+ addStructFields(innerarr.addObject(), names[i][k], genders[i][k], null);
+ }
+ }
+ return new ConfigPayload(slime).toInstance(StructtypesConfig.class, "");
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/DefaultValueApplierTest.java b/config/src/test/java/com/yahoo/vespa/config/DefaultValueApplierTest.java
new file mode 100644
index 00000000000..623d81993d9
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/DefaultValueApplierTest.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.Type;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class DefaultValueApplierTest {
+ public Slime apply(Slime slime, String ... extraFields) {
+ StringBuilder defBuilder = new StringBuilder();
+ defBuilder.append("namespace=test").append("\n");
+ defBuilder.append("str string").append("\n");
+ for (String field : extraFields) {
+ defBuilder.append(field).append("\n");
+ }
+ Cursor cursor = slime.get();
+ cursor.setString("str", "myvalue");
+ InnerCNode def = new DefParser("simpletypes", new StringReader(defBuilder.toString())).getTree();
+ DefaultValueApplier applier = new DefaultValueApplier();
+ return applier.applyDefaults(slime, def);
+ }
+
+ public Slime apply(String ... extraFields) {
+ Slime slime = new Slime();
+ slime.setObject();
+ return apply(slime, extraFields);
+ }
+
+ @Test
+ public void require_that_simple_defaults_are_applied() {
+ Slime slime = apply("strdef string default=\"foo\"");
+ assertTrue(slime.get().field("str").valid());
+ assertThat(slime.get().field("str").asString(), is("myvalue"));
+ assertTrue(slime.get().field("strdef").valid());
+ assertThat(slime.get().field("strdef").asString(), is("foo"));
+
+ }
+
+ @Test
+ public void require_that_struct_fields_defaults_are_applied() {
+ Slime slime = apply("nested.str string default=\"bar\"");
+ assertTrue(slime.get().field("nested").valid());
+ assertTrue(slime.get().field("nested").field("str").valid());
+ assertThat(slime.get().field("nested").field("str").asString(), is("bar"));
+ }
+
+ @Test
+ public void require_that_arrays_of_struct_fields_defaults_are_applied() {
+ Slime payload = new Slime();
+ Cursor cursor = payload.setObject();
+ cursor.setArray("nestedarr").addObject().setString("foo", "myfoo");
+ Slime slime = apply(payload, "nestedarr[].foo string", "nestedarr[].bar string default=\"bim\"");
+
+ assertTrue(slime.get().field("nestedarr").valid());
+ assertThat(slime.get().field("nestedarr").entries(), is(1));
+ assertTrue(slime.get().field("nestedarr").entry(0).field("foo").valid());
+ assertThat(slime.get().field("nestedarr").entry(0).field("foo").asString(), is("myfoo"));
+ assertTrue(slime.get().field("nestedarr").entry(0).field("bar").valid());
+ assertThat(slime.get().field("nestedarr").entry(0).field("bar").asString(), is("bim"));
+ }
+
+ @Test
+ public void require_that_arrays_of_struct_fields_defaults_when_empty() {
+ Slime payload = new Slime();
+ payload.setObject();
+ Slime slime = apply(payload, "nestedarr[].foo string", "nestedarr[].bar string default=\"bim\"");
+
+ assertTrue(slime.get().field("nestedarr").valid());
+ assertThat(slime.get().field("nestedarr").entries(), is(0));
+ assertThat(slime.get().field("nestedarr").type(), is(Type.ARRAY));
+ }
+
+ @Test
+ public void require_that_maps_of_struct_fields_defaults_are_applied() {
+ Slime payload = new Slime();
+ Cursor cursor = payload.setObject();
+ cursor.setObject("nestedmap").setObject("mykey").setString("foo", "myfoo");
+ Slime slime = apply(payload, "nestedmap{}.foo string", "nestedmap{}.bar string default=\"bim\"");
+
+ assertTrue(slime.get().field("nestedmap").valid());
+ assertThat(slime.get().field("nestedmap").fields(), is(1));
+ assertTrue(slime.get().field("nestedmap").field("mykey").field("foo").valid());
+ assertThat(slime.get().field("nestedmap").field("mykey").field("foo").asString(), is("myfoo"));
+ assertTrue(slime.get().field("nestedmap").field("mykey").field("bar").valid());
+ assertThat(slime.get().field("nestedmap").field("mykey").field("bar").asString(), is("bim"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ErrorCodeTest.java b/config/src/test/java/com/yahoo/vespa/config/ErrorCodeTest.java
new file mode 100644
index 00000000000..249a5ab6123
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ErrorCodeTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1.9
+ */
+public class ErrorCodeTest {
+ @Test
+ public void basic() {
+ assertThat(ErrorCode.getName(ErrorCode.INTERNAL_ERROR), is("INTERNAL_ERROR"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_CONFIG_MD5), is("ILLEGAL_CONFIG_MD5"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_CONFIGID), is("ILLEGAL_CONFIGID"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_DEF_MD5), is("ILLEGAL_DEF_MD5"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_GENERATION), is("ILLEGAL_GENERATION"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_NAME), is("ILLEGAL_NAME"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_SUB_FLAG), is("ILLEGAL_SUBSCRIBE_FLAG"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_TIMEOUT), is("ILLEGAL_TIMEOUT"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_VERSION), is("ILLEGAL_VERSION"));
+ assertThat(ErrorCode.getName(ErrorCode.OUTDATED_CONFIG), is("OUTDATED_CONFIG"));
+ assertThat(ErrorCode.getName(ErrorCode.UNKNOWN_CONFIG), is("UNKNOWN_CONFIG"));
+ assertThat(ErrorCode.getName(ErrorCode.UNKNOWN_DEF_MD5), is("UNKNOWN_DEF_MD5"));
+ assertThat(ErrorCode.getName(ErrorCode.UNKNOWN_DEFINITION), is("UNKNOWN_DEFINITION"));
+ assertThat(ErrorCode.getName(ErrorCode.UNKNOWN_VESPA_VERSION), is("UNKNOWN_VESPA_VERSION"));
+ assertThat(ErrorCode.getName(ErrorCode.INCONSISTENT_CONFIG_MD5), is("INCONSISTENT_CONFIG_MD5"));
+ assertThat(ErrorCode.getName(ErrorCode.ILLEGAL_CLIENT_HOSTNAME), is("ILLEGAL_CLIENT_HOSTNAME"));
+
+ assertThat(ErrorCode.getName(12345), is("Unknown error"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/ErrorTypeTest.java b/config/src/test/java/com/yahoo/vespa/config/ErrorTypeTest.java
new file mode 100644
index 00000000000..d8af6584eca
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/ErrorTypeTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ErrorTypeTest {
+
+ @Test
+ public void testErrorType() {
+ assertThat(ErrorType.getErrorType(com.yahoo.jrt.ErrorCode.CONNECTION), is(ErrorType.TRANSIENT));
+ assertThat(ErrorType.getErrorType(com.yahoo.jrt.ErrorCode.TIMEOUT), is(ErrorType.TRANSIENT));
+ assertThat(ErrorType.getErrorType(ErrorCode.UNKNOWN_CONFIG), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.UNKNOWN_DEFINITION), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.UNKNOWN_DEF_MD5), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_NAME), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_VERSION), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_CONFIGID), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_DEF_MD5), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_CONFIG_MD5), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_TIMEOUT), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_GENERATION), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_SUB_FLAG), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.OUTDATED_CONFIG), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.INTERNAL_ERROR), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(ErrorCode.ILLEGAL_SUB_FLAG), is(ErrorType.FATAL));
+ assertThat(ErrorType.getErrorType(0xdeadc0de), is(ErrorType.FATAL));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/GenericConfigBuilderTest.java b/config/src/test/java/com/yahoo/vespa/config/GenericConfigBuilderTest.java
new file mode 100644
index 00000000000..f745c70fe1b
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/GenericConfigBuilderTest.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.subscription.ConfigInstanceUtil;
+import com.yahoo.slime.JsonFormat;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class GenericConfigBuilderTest {
+ @Test
+ public void require_that_builder_can_be_overridden() throws IOException {
+ ConfigPayloadBuilder ba = new ConfigPayloadBuilder();
+ ba.setField("foo", "bar");
+ ConfigPayloadBuilder bb = new ConfigPayloadBuilder();
+ bb.setField("foo", "baz");
+ ConfigPayloadBuilder bc = new ConfigPayloadBuilder();
+ bc.setField("foo", "bim");
+ GenericConfig.GenericConfigBuilder a = new GenericConfig.GenericConfigBuilder(null, ba);
+ GenericConfig.GenericConfigBuilder b = new GenericConfig.GenericConfigBuilder(null, bb);
+ GenericConfig.GenericConfigBuilder c = new GenericConfig.GenericConfigBuilder(null, bc);
+ assertThat(getString(a), is("{\"foo\":\"bar\"}"));
+ assertThat(getString(b), is("{\"foo\":\"baz\"}"));
+ assertThat(getString(c), is("{\"foo\":\"bim\"}"));
+ ConfigInstanceUtil.setValues(a, b);
+ assertThat(getString(a), is("{\"foo\":\"baz\"}"));
+ assertThat(getString(b), is("{\"foo\":\"baz\"}"));
+ assertThat(getString(c), is("{\"foo\":\"bim\"}"));
+ ConfigInstanceUtil.setValues(c, a);
+ assertThat(getString(a), is("{\"foo\":\"baz\"}"));
+ assertThat(getString(b), is("{\"foo\":\"baz\"}"));
+ assertThat(getString(c), is("{\"foo\":\"baz\"}"));
+ }
+
+ private String getString(GenericConfig.GenericConfigBuilder builder) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ builder.getPayload().serialize(baos, new JsonFormat(true));
+ return baos.toString();
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java b/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java
new file mode 100644
index 00000000000..438d8edb430
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/JRTConnectionPoolTest.java
@@ -0,0 +1,124 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.subscription.ConfigSourceSet;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the JRTConnectionPool class.
+ *
+ * @author <a href="mailto:gunnarga@yahoo-inc.com">Gunnar Gauslaa Bergem</a>
+ * @author musum
+ */
+public class JRTConnectionPoolTest {
+ private static final List<String> sources = new ArrayList<>((Arrays.asList("host0", "host1", "host2")));
+
+ /**
+ * Tests that hash-based selection through the list works.
+ */
+ @Test
+ public void test_random_selection_of_sourceBasicHashBasedSelection() {
+ JRTConnectionPool sourcePool = new JRTConnectionPool(sources);
+ assertThat(sourcePool.toString(), is("Address: host0\nAddress: host1\nAddress: host2\n"));
+
+ Map<String, Integer> sourceOccurrences = new HashMap<>();
+ for (int i = 0; i < 1000; i++) {
+ final String address = sourcePool.setNewCurrentConnection().getAddress();
+ if (sourceOccurrences.containsKey(address)) {
+ sourceOccurrences.put(address, sourceOccurrences.get(address) + 1);
+ } else {
+ sourceOccurrences.put(address, 1);
+ }
+ }
+ for (int i = 0; i < sourcePool.getSize(); i++) {
+ assertTrue(sourceOccurrences.get(sourcePool.getSources().get(i).getAddress()) > 200);
+ }
+ }
+
+ /**
+ * Tests that when there are two sources and several clients
+ * the sources will be chosen with about the same probability.
+ */
+ @Test
+ public void testManySources() {
+ Map<String, Integer> timesUsed = new LinkedHashMap<>();
+
+ List<String> twoSources = new ArrayList<>();
+
+ twoSources.add("host0");
+ twoSources.add("host1");
+ JRTConnectionPool sourcePool = new JRTConnectionPool(twoSources);
+
+ int count = 1000;
+ for (int i = 0; i < count; i++) {
+ String address = sourcePool.setNewCurrentConnection().getAddress();
+ if (timesUsed.containsKey(address)) {
+ int times = timesUsed.get(address);
+ timesUsed.put(address, times + 1);
+ } else {
+ timesUsed.put(address, 1);
+ }
+ }
+ assertConnectionDistributionIsFair(timesUsed);
+ }
+
+ // Tests that the number of times each connection is used is close to equal
+ private void assertConnectionDistributionIsFair(Map<String, Integer> connectionsUsedPerHost) {
+ double devianceDueToRandomSourceSelection = 0.13;
+ final int size = 1000;
+ int minHostCount = (int) (size/2 * (1 - devianceDueToRandomSourceSelection));
+ int maxHostCount = (int) (size/2 * (1 + devianceDueToRandomSourceSelection));
+
+ for (Map.Entry<String, Integer> entry : connectionsUsedPerHost.entrySet()) {
+ Integer timesUsed = entry.getValue();
+ assertTrue("Host 0 used " + timesUsed + " times, expected to be < " + maxHostCount, timesUsed < maxHostCount);
+ assertTrue("Host 0 used " + timesUsed + " times, expected to be > " + minHostCount, timesUsed > minHostCount);
+ }
+ }
+
+ /**
+ * Tests that updating config sources works.
+ */
+ @Test
+ public void updateSources() {
+ List<String> twoSources = new ArrayList<>();
+
+ twoSources.add("host0");
+ twoSources.add("host1");
+ JRTConnectionPool sourcePool = new JRTConnectionPool(twoSources);
+
+ ConfigSourceSet sourcesBefore = sourcePool.getSourceSet();
+
+ // Update to the same set, should be equal
+ sourcePool.updateSources(twoSources);
+ assertThat(sourcesBefore, is(sourcePool.getSourceSet()));
+
+ // Update to new set
+ List<String> newSources = new ArrayList<>();
+ newSources.add("host2");
+ newSources.add("host3");
+ sourcePool.updateSources(newSources);
+ ConfigSourceSet newSourceSet = sourcePool.getSourceSet();
+ assertNotNull(newSourceSet);
+ assertThat(newSourceSet.getSources().size(), is(2));
+ assertThat(newSourceSet, is(not(sourcesBefore)));
+ assertTrue(newSourceSet.getSources().contains("host2"));
+ assertTrue(newSourceSet.getSources().contains("host3"));
+
+ // Update to new set with just one host
+ List<String> newSources2 = new ArrayList<>();
+ newSources2.add("host4");
+ sourcePool.updateSources(newSources2);
+ ConfigSourceSet newSourceSet2 = sourcePool.getSourceSet();
+ assertNotNull(newSourceSet2);
+ assertThat(newSourceSet2.getSources().size(), is(1));
+ assertThat(newSourceSet2, is(not(newSourceSet)));
+ assertTrue(newSourceSet2.getSources().contains("host4"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/LZ4CompressionTest.java b/config/src/test/java/com/yahoo/vespa/config/LZ4CompressionTest.java
new file mode 100644
index 00000000000..5fb7c7a9a97
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/LZ4CompressionTest.java
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import net.jpountz.lz4.LZ4Compressor;
+import net.jpountz.lz4.LZ4Factory;
+import net.jpountz.lz4.LZ4SafeDecompressor;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+
+/**
+ * To run this test, place a payload in src/test/ca.json. The file is not checked in because it is huge.
+ *
+ * @author lulf
+ * @since 5.12
+ */
+public class LZ4CompressionTest {
+ private static LZ4Factory factory = LZ4Factory.safeInstance();
+
+ @Test
+ @Ignore
+ public void testCompression() throws IOException {
+ byte[] data = getInput();
+ System.out.println("High compressor");
+ for (int i = 0; i < 10; i++) {
+ timeCompressor(factory.highCompressor(), data);
+ }
+ System.out.println("Fast compressor");
+ for (int i = 0; i < 10; i++) {
+ timeCompressor(factory.fastCompressor(), data);
+ }
+ }
+
+ private byte[] getInput() throws IOException {
+ byte[] data = Files.readAllBytes(FileSystems.getDefault().getPath("src/test/ca.json"));
+ System.out.println("Input size: " + data.length);
+ return data;
+ }
+
+ private void timeCompressor(LZ4Compressor lz4Compressor, byte[] data) {
+ long start = System.currentTimeMillis();
+ byte[] compressed = lz4Compressor.compress(data);
+ long end = System.currentTimeMillis();
+ System.out.println("Compression took " + (end - start) + " millis, and size of data is " + compressed.length + " bytes");
+ }
+
+ @Test
+ @Ignore
+ public void testDecompression() throws IOException {
+ byte[] data = getInput();
+ byte[] outputbuffer = new byte[data.length];
+ byte[] hcCompressedData = factory.highCompressor().compress(data);
+ System.out.println("High compressor");
+ for (int i = 0; i < 10; i++) {
+ timeDecompressor(hcCompressedData, factory.safeDecompressor(), outputbuffer);
+ }
+ byte[] fastCompressedData = factory.fastCompressor().compress(data);
+ System.out.println("Fast compressor");
+ for (int i = 0; i < 10; i++) {
+ timeDecompressor(fastCompressedData, factory.safeDecompressor(), outputbuffer);
+ }
+ }
+
+ private void timeDecompressor(byte[] compressedData, LZ4SafeDecompressor decompressor, byte[] outputbuffer) {
+ long start = System.currentTimeMillis();
+ decompressor.decompress(compressedData, outputbuffer);
+ long end = System.currentTimeMillis();
+ System.out.println("Decompression took " + (end - start) + " millis");
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/LZ4PayloadCompressorTest.java b/config/src/test/java/com/yahoo/vespa/config/LZ4PayloadCompressorTest.java
new file mode 100644
index 00000000000..4b5e5ae07fe
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/LZ4PayloadCompressorTest.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.text.Utf8;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.19
+ */
+public class LZ4PayloadCompressorTest {
+ @Test
+ public void testCompression() {
+ assertCompression("hei hallo der");
+ assertCompression("");
+ assertCompression("{}");
+ }
+
+ private void assertCompression(String input) {
+ LZ4PayloadCompressor compressor = new LZ4PayloadCompressor();
+ byte[] data = Utf8.toBytes(input);
+ byte[] compressed = compressor.compress(data);
+ byte[] output = new byte[data.length];
+ compressor.decompress(compressed, output);
+ assertThat(data, is(output));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java b/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java
new file mode 100644
index 00000000000..7a316838334
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/RawConfigTest.java
@@ -0,0 +1,123 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.text.Utf8String;
+import com.yahoo.vespa.config.protocol.*;
+import com.yahoo.vespa.config.protocol.VespaVersion;
+import com.yahoo.vespa.config.util.ConfigUtils;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1.9
+ */
+public class RawConfigTest {
+
+ private static final ConfigKey<?> key = new ConfigKey<>("foo", "id", "bar");
+ private static List<String> defContent = Arrays.asList("version=1", "anInt int");
+ private static final String defMd5 = ConfigUtils.getDefMd5FromRequest("", defContent);
+ private static final String configMd5 = "012345";
+ private static Payload payload = Payload.from(new Utf8String("anInt 1"), CompressionInfo.uncompressed());
+ private static long generation = 1L;
+
+ @Test
+ public void basic() {
+ RawConfig config = new RawConfig(key, defMd5);
+ assertEquals(config.getKey(), key);
+ assertThat(config.getDefMd5(), is(defMd5));
+ assertThat(config.getName(), is("foo"));
+ assertThat(config.getDefNamespace(), is("bar"));
+ assertThat(config.getConfigId(), is("id"));
+
+ assertThat(config.isError(), is(false));
+
+ // Copy constructor
+ RawConfig copiedConfig = new RawConfig(config);
+ assertThat(copiedConfig, is(config));
+
+ assertThat(config.toString(), is("bar.foo," + defMd5 + ",id,,0,null"));
+ assertThat(config.getVespaVersion(), is(Optional.empty()));
+ }
+
+ @Test
+ public void testEquals() {
+ RawConfig config = new RawConfig(key, defMd5);
+
+ assertThat(config, is(new RawConfig(key, defMd5)));
+ assertThat(config, is(not(new RawConfig(key, "a")))); // different def md5
+ assertThat(config.hashCode(), is(new RawConfig(key, defMd5).hashCode()));
+ assertThat(config.hashCode(), is(not(new RawConfig(key, "a").hashCode()))); // different def md5
+
+ // different generation
+ config = new RawConfig(key, defMd5, payload, configMd5, generation, defContent, Optional.empty());
+ RawConfig config2 = new RawConfig(key, defMd5, payload, configMd5, 2L, defContent, Optional.empty());
+ assertThat(config, is(not(config2)));
+ assertThat(config.hashCode(), is(not(config2.hashCode())));
+
+ // different config md5 and with vespa version
+ final VespaVersion vespaVersion = VespaVersion.fromString("5.37.38");
+ RawConfig config3 = new RawConfig(key, defMd5, payload, "9999", generation, defContent, Optional.of(vespaVersion));
+ assertThat(config, is(not(config3)));
+ assertThat(config.hashCode(), is(not(config3.hashCode())));
+ // Check that vespa version is set correctly
+ assertThat(config3.getVespaVersion().get().toString(), is(vespaVersion.toString()));
+ assertThat(config.getVespaVersion(), is(not(config3.getVespaVersion())));
+
+ // null config
+ assertFalse(config.equals(null));
+
+ // different type of object
+ assertFalse(config.equals(key));
+
+ // errors
+ RawConfig errorConfig1 = new RawConfig(key, defMd5, payload, configMd5, generation, 1, defContent, Optional.empty());
+ assertThat(errorConfig1, is(errorConfig1));
+ assertThat(config, is(not(errorConfig1)));
+ assertThat(config.hashCode(), is(not(errorConfig1.hashCode())));
+ assertThat(errorConfig1, is(errorConfig1));
+ RawConfig errorConfig2 = new RawConfig(key, defMd5, payload, configMd5, generation, 2, defContent, Optional.empty());
+ assertThat(errorConfig1, is(not(errorConfig2)));
+ assertThat(errorConfig1.hashCode(), is(not(errorConfig2.hashCode())));
+ }
+
+ @Test
+ public void payload() {
+ RawConfig config = new RawConfig(key, defMd5, payload, configMd5, generation, defContent, Optional.empty());
+ assertThat(config.getPayload(), is(payload));
+ assertThat(config.getConfigMd5(), is(configMd5));
+ assertThat(config.getGeneration(), is(generation));
+ assertThat(config.getDefContent(), is(defContent));
+ }
+
+ @Test
+ public void require_correct_defmd5() {
+ final String defMd5ForEmptyDefContent = "d41d8cd98f00b204e9800998ecf8427e";
+
+ RawConfig config = new RawConfig(key, null, payload, configMd5, generation, defContent, Optional.empty());
+ assertThat(config.getDefMd5(), is(defMd5));
+ config = new RawConfig(key, "", payload, configMd5, generation, defContent, Optional.empty());
+ assertThat(config.getDefMd5(), is(defMd5));
+ config = new RawConfig(key, defMd5, payload, configMd5, generation, defContent, Optional.empty());
+ assertThat(config.getDefMd5(), is(defMd5));
+ config = new RawConfig(key, null, payload, configMd5, generation, null, Optional.empty());
+ assertNull(config.getDefMd5());
+ config = new RawConfig(key, null, payload, configMd5, generation, Arrays.asList(""), Optional.empty());
+ assertThat(config.getDefMd5(), is(defMd5ForEmptyDefContent));
+ config = new RawConfig(key, "", payload, configMd5, generation, null, Optional.empty());
+ assertThat(config.getDefMd5(), is(""));
+ config = new RawConfig(key, "", payload, configMd5, generation, Arrays.asList(""), Optional.empty());
+ assertThat(config.getDefMd5(), is(defMd5ForEmptyDefContent));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/RequestValidationTest.java b/config/src/test/java/com/yahoo/vespa/config/RequestValidationTest.java
new file mode 100644
index 00000000000..1e237145b02
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/RequestValidationTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.vespa.config.protocol.RequestValidation;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class RequestValidationTest {
+
+ @Test
+ public void testVerifyName() {
+ assertTrue(RequestValidation.verifyName("foo"));
+ assertTrue(RequestValidation.verifyName("Foo"));
+ assertTrue(RequestValidation.verifyName("foo-bar"));
+ assertFalse(RequestValidation.verifyName("1foo"));
+ assertTrue(RequestValidation.verifyName("foo_bar"));
+ }
+
+ @Test
+ public void testVerifyDefMd5() {
+ assertTrue(RequestValidation.verifyMd5(""));
+ assertTrue(RequestValidation.verifyMd5("e8f0c01c7c3dcb8d3f62d7ff777fce6b"));
+ assertTrue(RequestValidation.verifyMd5("e8f0c01c7c3dcb8d3f62d7ff777fce6B"));
+ assertFalse(RequestValidation.verifyMd5("aaaaaaaaaaaaaaaaaa"));
+ assertFalse(RequestValidation.verifyMd5("-8f0c01c7c3dcb8d3f62d7ff777fce6b"));
+ }
+
+ @Test
+ public void testVerifyTimeout() {
+ assertTrue(RequestValidation.verifyTimeout(1000L));
+ assertFalse(RequestValidation.verifyTimeout(-1000L));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/SlimeUtilsTest.java b/config/src/test/java/com/yahoo/vespa/config/SlimeUtilsTest.java
new file mode 100644
index 00000000000..742fed77c36
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/SlimeUtilsTest.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.text.Utf8;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.8
+ */
+public class SlimeUtilsTest {
+ @Test
+ public void test_copying_slime_types_into_cursor() {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("foo", "foobie");
+ Cursor subobj = root.setObject("bar");
+
+ Slime slime2 = new Slime();
+ Cursor root2 = slime2.setObject();
+ root2.setString("a", "a");
+ root2.setLong("b", 2);
+ root2.setBool("c", true);
+ root2.setDouble("d", 3.14);
+ root2.setData("e", new byte[]{0x64});
+ root2.setNix("f");
+
+ SlimeUtils.copyObject(slime2.get(), subobj);
+
+ assertThat(root.toString(), is("{\"foo\":\"foobie\",\"bar\":{\"a\":\"a\",\"b\":2,\"c\":true,\"d\":3.14,\"e\":\"0x64\",\"f\":null}}"));
+ }
+
+ @Test
+ public void test_copying_slime_arrays_into_cursor() {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("foo", "foobie");
+ Cursor subobj = root.setObject("bar");
+
+ Slime slime2 = new Slime();
+ Cursor root2 = slime2.setObject();
+ Cursor array = root2.setArray("a");
+ array.addString("foo");
+ array.addLong(4);
+ array.addBool(true);
+ array.addDouble(3.14);
+ array.addNix();
+ array.addData(new byte[]{0x64});
+ Cursor objinner = array.addObject();
+ objinner.setString("inner", "binner");
+
+ SlimeUtils.copyObject(slime2.get(), subobj);
+
+ assertThat(root.toString(), is("{\"foo\":\"foobie\",\"bar\":{\"a\":[\"foo\",4,true,3.14,null,\"0x64\",{\"inner\":\"binner\"}]}}"));
+ }
+
+ @Test
+ public void test_slime_to_json() throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("foo", "foobie");
+ root.setObject("bar");
+ String json = Utf8.toString(SlimeUtils.toJsonBytes(slime));
+ assertThat(json, is("{\"foo\":\"foobie\",\"bar\":{}}"));
+ }
+
+ @Test
+ public void test_json_to_slime() {
+ byte[] json = Utf8.toBytes("{\"foo\":\"foobie\",\"bar\":{}}");
+ Slime slime = SlimeUtils.jsonToSlime(json);
+ assertThat(slime.get().field("foo").asString(), is("foobie"));
+ assertTrue(slime.get().field("bar").valid());
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/SourceTest.java b/config/src/test/java/com/yahoo/vespa/config/SourceTest.java
new file mode 100644
index 00000000000..989131c8256
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/SourceTest.java
@@ -0,0 +1,166 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import com.yahoo.config.ConfigurationRuntimeException;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequest;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequestV3;
+import com.yahoo.vespa.config.protocol.Payload;
+import com.yahoo.vespa.config.protocol.Trace;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SourceTest {
+
+ @Test
+ public void testSourceInterface() {
+ MockSourceConfig config = new MockSourceConfig(new ConfigKey<>(SimpletypesConfig.class, "foobio"));
+ assertThat(config.getKey().getConfigId(), is("foobio"));
+ config.setConfig(JRTClientConfigRequestV3.createWithParams(config.getKey(), DefContent.fromList(config.getDefContent()), "host", config.getDefMd5(), config.getGeneration(), 1000, Trace.createNew(), CompressionType.LZ4, Optional.empty()));
+ MockSource src = new MockSource(config);
+ assertThat(src.getState(), is(Source.State.NEW));
+ src.open();
+ assertTrue(src.opened);
+ assertTrue(src.getconfigged);
+ assertThat(src.getState(), is(Source.State.OPEN_PENDING));
+ src.open();
+ assertThat(src.getState(), is(Source.State.OPEN_PENDING));
+ src.getconfigged = false;
+ src.getConfig();
+ assertTrue(src.getconfigged);
+ assertThat(src.getState(), is(Source.State.OPEN_PENDING));
+ assertTrue(config.setConfigCalled);
+ assertTrue(src.openTimestamp > 0);
+ config.notifyInitMonitor();
+ config.setGeneration(4);
+ src.cancel();
+ assertTrue(src.canceled);
+ assertThat(src.getState(), is(Source.State.CANCEL_REQUESTED));
+ src.setState(Source.State.CANCELLED);
+ try {
+ src.open();
+ fail("Expected exception");
+ } catch (ConfigurationRuntimeException e) {
+ }
+ src.getconfigged = false;
+ src.getConfig();
+ assertFalse(src.getconfigged);
+ src.canceled = false;
+ src.cancel();
+ assertFalse(src.canceled);
+ }
+
+ public static class MockSource extends Source {
+ boolean opened, getconfigged, canceled = false;
+
+ public MockSource(SourceConfig sourceConfig) {
+ super(sourceConfig);
+ }
+
+ @Override
+ public void myOpen() {
+ opened = true;
+ }
+
+ @Override
+ protected void myGetConfig() {
+ getconfigged = true;
+ }
+
+ @Override
+ public void myCancel() {
+ canceled = true;
+ }
+ }
+
+ private static class MockSourceConfig implements SourceConfig {
+
+ boolean notifyCalled = false;
+ ConfigKey<?> key = null;
+ boolean setConfigCalled = false;
+ long generation = -1;
+ Payload payload;
+
+ public MockSourceConfig(ConfigKey<?> key) {
+ this.key = key;
+ }
+
+ @Override
+ public void notifyInitMonitor() {
+ notifyCalled = true;
+ }
+
+ @Override
+ public void setConfig(com.yahoo.vespa.config.protocol.JRTClientConfigRequest req) {
+ key = req.getConfigKey();
+ setConfigCalled = true;
+ }
+
+ @Override
+ public void setGeneration(long generation) {
+ this.generation = generation;
+ }
+
+ @Override
+ public String getDefName() {
+ return key.getName();
+ }
+
+ @Override
+ public String getDefNamespace() {
+ return key.getNamespace();
+ }
+
+ @Override
+ public String getDefVersion() {
+ return "";
+ }
+
+ @Override
+ public List<String> getDefContent() {
+ return Arrays.asList("foo");
+ }
+
+ @Override
+ public String getDefMd5() {
+ return key.getMd5();
+ }
+
+ @Override
+ public String getConfigId() {
+ return key.getConfigId();
+ }
+
+ @Override
+ public ConfigKey<?> getKey() {
+ return key;
+ }
+
+ @Override
+ public String getConfigMd5() {
+ return "bar";
+ }
+
+ @Override
+ public long getGeneration() {
+ return 0;
+ }
+
+ @Override
+ public RawConfig getConfig() {
+ return new RawConfig(getKey(), getDefMd5(), payload, getConfigMd5(), generation, getDefContent(), Optional.empty());
+ }
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/TimingValuesTest.java b/config/src/test/java/com/yahoo/vespa/config/TimingValuesTest.java
new file mode 100644
index 00000000000..1bae6c79ccb
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/TimingValuesTest.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Note: Most of the functionality is tested implicitly by other tests
+ *
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class TimingValuesTest {
+ @Test
+ public void basic() {
+ TimingValues tv = new TimingValues();
+ TimingValues tv2 = new TimingValues(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1);
+ assertThat(tv.getRandom(), is(not(tv2.getRandom())));
+ TimingValues copy = new TimingValues(tv2);
+ assertThat(copy.toString(), is(tv2.toString())); // No equals method, just using toString to compare
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/buildergen/ConfigBuilderGeneratorTest.java b/config/src/test/java/com/yahoo/vespa/config/buildergen/ConfigBuilderGeneratorTest.java
new file mode 100644
index 00000000000..3d24372c40c
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/buildergen/ConfigBuilderGeneratorTest.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.buildergen;
+
+import com.google.common.io.Files;
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.codegen.ConfigGenerator;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.ConfigPayloadApplier;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URISyntaxException;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigBuilderGeneratorTest {
+ @Test
+ public void require_that_custom_classes_can_be_generated() throws URISyntaxException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
+ String[] schema = new String[] {
+ "namespace=foo.bar",
+ "intval int",
+ "stringval string"
+ };
+ File tempDir = Files.createTempDir();
+ ConfigDefinitionKey key = new ConfigDefinitionKey("quux", "foo.bar");
+ ConfigCompiler compiler = new LazyConfigCompiler(tempDir);
+ ConfigInstance.Builder builder = compiler.compile(new ConfigDefinition(key.getName(), schema).generateClass()).newInstance();
+ assertNotNull(builder);
+ ConfigPayloadApplier<?> payloadApplier = new ConfigPayloadApplier<>(builder);
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ root.setString("intval", "3");
+ root.setString("stringval", "Hello, world");
+ payloadApplier.applyPayload(new ConfigPayload(slime));
+ String className = ConfigGenerator.createClassName(key.getName());
+ ConfigInstance instance = (ConfigInstance) builder.getClass().getClassLoader().loadClass("com.yahoo." + key.getNamespace() + "." + className).getConstructor(new Class<?>[]{builder.getClass()}).newInstance(builder);
+ assertNotNull(instance);
+ assertThat(instance.toString(), is("intval 3\nstringval \"Hello, world\""));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/classes/app.1.def b/config/src/test/java/com/yahoo/vespa/config/classes/app.1.def
new file mode 100644
index 00000000000..63e986b58ab
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/classes/app.1.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+
+message string default="Hello!"
+
+times int default=1
+
+a[].name string
diff --git a/config/src/test/java/com/yahoo/vespa/config/classes/qr-logging.def b/config/src/test/java/com/yahoo/vespa/config/classes/qr-logging.def
new file mode 100644
index 00000000000..57ad0050a26
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/classes/qr-logging.def
@@ -0,0 +1,34 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=3
+logger string default="com.yahoo"
+# Either QueryAccessLog for a regular Vespa access log, or YApacheAccessLog for a log on yApache format
+speciallog[].name string
+
+# Leave as ""
+speciallog[].type string
+
+speciallog[].filehandler.name string default=""
+
+# File name patterns supporting the expected time variables
+speciallog[].filehandler.pattern string default=".%Y%m%d%H%M%S"
+
+speciallog[].filehandler.rotation string default="0 60 ..."
+
+# Defines how file rotation is done. There are two options:
+#
+# "date" :
+# The active log file is given the name resulting from pattern (but in this case "pattern" must yield a
+# time-dependent name. In addition, a symlink is created pointing to the newest file.
+# The symlink is given the name of the symlink parameter (or the name of this service
+# if no parameter is given.
+#
+# "sequence" :
+# The active log file is given the name
+# defined by "pattern" (which in this case will likely just be a constant string).
+# At rotation, this file is given the name pattern.N where N is 1 + the largest integer found by
+# extracting the integers from all files ending by .Integer in the same directory
+#
+speciallog[].filehandler.rotatescheme string default="date"
+
+speciallog[].cachehandler.name string default=""
+speciallog[].cachehandler.size int default=1000
diff --git a/config/src/test/java/com/yahoo/vespa/config/classes/qr-templates.3.def b/config/src/test/java/com/yahoo/vespa/config/classes/qr-templates.3.def
new file mode 100644
index 00000000000..8483bf03c44
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/classes/qr-templates.3.def
@@ -0,0 +1,142 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=3
+
+## Directory for temporary files
+directory string default="tmp/templates"
+
+hey[].ho[].lets[].go string default="ramones"
+hey[].ho[].lets[].fishing int default=-4 range=[-8,0]
+hey[].ho[].lets[].gone int default=200 range=[0,1000000]
+hey[].ho[].lets[].ref reference
+hey[].ho[].gone int default=2000 range=[-10,2000000]
+hey[].ho[].going bool default=false
+hey[].ho[].wash double default=345.3
+hey[].ho[].me double default=-45.5 range=[-234.43,0]
+hey[].ho[].now double default=-34 range=[-234,0]
+hi[].there[].e enum { BATCH, REALTIME, INCREMENTAL} default=BATCH
+hi[].ther[].f enum { BATCH, REALTIME } default=BATCH
+
+#hey[] int
+mode enum { BATCH, REALTIME, INCREMENTAL} default=BATCH
+bar.arrline[] string
+az[] double
+bar.arline[] int range=[0,999]
+#bar[].version int
+b1[].b2[].b3[].b4[] bool
+## Capacities for all storage nodes
+capacity[] double range=[0,100]
+
+longVal long
+longWithDefault long default=9876543210
+longWithRange long range=[-9000000000,0]
+longArr[] long
+longArrWithRange[] long range=[0,9000000000]
+
+fileVal file
+fileWithDefault file
+fileArr[] file
+
+washing double default=5 range=[-1.4,34.324432]
+washer double default=46 range=[-1.6,54]
+
+urlprefix string
+
+## Prefix to use in queries to choose a given template
+templateset[].urlprefix string
+
+## The MIME type of a given template
+templateset[].mimetype string default="text/html"
+
+## The character set of a given template
+templateset[].encoding string default="iso-8859-1"
+
+## Not used
+templateset[].rankprofile int default=0
+
+
+## Not used in 1.0
+templateset[].keepalive bool default=false
+
+## Header template. Always rendered.
+templateset[].headertemplate string
+
+## Footer template. Always rendered.
+templateset[].footertemplate string
+
+## Nohits template. Rendered if there are no hits in the result.
+templateset[].nohitstemplate string
+
+## Hit template. Rendered if there are hits in the result.
+templateset[].hittemplate string
+
+## Error template. Rendered if there is an error condition. This is
+## not mutually exclusive with the (no)hit templates as such.
+templateset[].errortemplate string
+
+groupsheadertemplate string default="[DEFAULT]"
+
+## Aggregated groups header template.
+## Default rendering is used if missing
+templateset[].groupsheadertemplate string default="[DEFAULT]"
+
+## Aggregated range group template.
+## Default rendering is used if missing
+templateset[].rangegrouptemplate string default="[DEFAULT]"
+
+## Aggregated exact group template
+## Default rendering is used if missing
+templateset[].exactgrouptemplate string default="[DEFAULT]"
+
+## Aggregated groups footer template.
+## Default rendering is used if missing
+templateset[].groupsfootertemplate string default="[DEFAULT]"
+
+## Tags used to highlight results, starting a bolded section.
+## An empty string means the template should no override what
+## was inserted by the search chain.
+templateset[].highlightstarttag string default=""
+## Tags used to highlight results, ending a bolded section
+## An empty string means the template should no override what
+## was inserted by the search chain.
+templateset[].highlightendtag string default=""
+## Tags used to highlight results, separating dynamic snippets
+## An empty string means the template should no override what
+## was inserted by the search chain.
+templateset[].highlightseptag string default=""
+
+## The summary class to use for this template if there is none
+## defined in the query.
+ilscript[].name string
+ilscript[].doctype string
+ilscript[].content[] string
+config[].id reference
+config[].autostart string default="no"
+musum string
+
+auran string
+
+route[].name string
+route[].selector string
+route[].feed string
+
+languages[] string
+languages2[] string
+foolang[].lang[] string
+
+# Maps
+myIntMap{} int
+myStringMap{} string
+myStructMap{}.myInt int
+myStructMap{}.myString string
+myStructMap{}.myIntDef int default=56
+myStructMap{}.myStringDef string default="g"
+
+myStructMap{}.myNestedLeafMap{} long
+myStructMap{}.myNestedArray[] long
+
+myStructMap{}.myNestedMap{}.myLong long
+myStructMap{}.myNestedMap{}.myLongDef long default=-100
+
+myStructMap{}.myStruct.a string
+myStructMap{}.myStruct.b string default="pizza"
+myStructMap{}.myStruct.c file
diff --git a/config/src/test/java/com/yahoo/vespa/config/classes/ranges.1.def b/config/src/test/java/com/yahoo/vespa/config/classes/ranges.1.def
new file mode 100644
index 00000000000..bd054750db5
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/classes/ranges.1.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+quux int default=5 range=[,]
+xyzzy double default=5 range=[,]
+longVal long default=5 range=[,]
diff --git a/config/src/test/java/com/yahoo/vespa/config/classes/testfoobar.12.def b/config/src/test/java/com/yahoo/vespa/config/classes/testfoobar.12.def
new file mode 100644
index 00000000000..11ac4e0b247
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/classes/testfoobar.12.def
@@ -0,0 +1,925 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=12-1-2
+
+longVal long
+longWithDefault long default=8589934592
+
+fileVal file
+fileWithDefault file
+
+vh[] double range=[-300,300]
+bg[] int default=0 range=[-10,10]
+gee[] string
+storage[].feeder[] string
+storage[].distributor[] string
+
+ju[].hu[].tu[] double default=-45 range=[0,1000.1]
+ju[].hu[].wu[] enum { HEY, HO} default=HO
+ju[].hu[].tang[] bool default=false
+ju[].hu[].clan[] int default=45 range=[-90,90]
+ju[].hu[].sann reference
+
+foo string
+headertemplate string
+## If true, the bitvector part of results with number of hits within
+## max bin size will be merged into array form before applying static
+## rank. NB: Setting this to true may reduce performance.
+applystaticranktosmallbitvectors bool default=true
+
+## Size of bitvector cache (in bytes).
+bitvectorcachesize int default=50000000
+
+## Size of boolean occurrence cache (in bytes).
+boolocccachesize int default=100000000
+
+## Size of dictionary cache (in bytes).
+dictcachesize int default=40000000
+
+onedimstruct[].val string
+
+## Size of document info cache (in bytes).
+documentinfocachesize int default=25000000
+
+## Size of filter occurrence cache (in bytes).
+## This cache is used to optimizse exclusion handling.
+## A too small size limit will cause serious performance degradation
+## if the staticfilterindexes keyword is used.
+filterocccachesize int default=30000000
+
+## Size of integer range bitvector cache (in bytes).
+intrangebitvectorcachesize int default=50000000
+
+## Size of integer occurrence cache (in bytes).
+intocccachesize int default=100000000
+
+## Size of phrase occurrence cache (in bytes).
+phrasecachesize int default=150000000
+
+## Size of phrase occurrence index cache (in bytes).
+phraseidxcachesize int default=20000000
+
+## Size of position occurrence cache (in bytes).
+posocccachesize int default=100000000
+
+## The filename of a file specifying which hosts that should have
+## access to the internal web-server of the fsearch process.
+accesslist string default=""
+
+## The filename of the log file for HTTP access.
+accesslog string default=""
+
+## The number of threads to perform async occurrence fetch, i.e.
+## read from posocc and boolocc files and possibly generation of
+## posocc/boolocc arrays for phrases. Async occurrence fetches
+## will use more system CPU but can reduce latency on lightly
+## loaded systems.
+asyncfetchocc int default=0
+
+## specifies the high limit of the ranked result bin.
+## If the percentage of the resultset specified in binsize is higher
+## than this limit, this will be the max size.
+binhigh int default=2147483647
+
+## specifies the lowest possible size of a ranked result. This is
+## the lower ramp of the percentage specified in the binsize variable.
+binlow int default=10000
+
+## specifies the size of the ranked results as a percentage of the
+## total result set size. The percentage can be ramped off with the
+## binlow and binhigh variables. NB: Setting this to 100.0 may lead to
+## seriously reduced performance.
+binsize double default=100.0
+
+## Check cache lines beyond offset + maxhits for blacklisting
+## in order to provide correct "totalhits" for queries with many hits.
+checktrailingcachelines bool default=false
+
+## specifies the number of result entries that are processed when
+## doing site collapsing. Site collapsing is performed by reordering
+## the first collapseentries hits in the result set, listing
+## the best hit from each site first.
+collapseentries int default=200 range=[0,1000000]
+
+## Specifies what is the default field used for collapse
+defaultcollapse string default=""
+
+## specifies the rank penalty for additional hits from a site found
+## during site collapsing. When additional hits from a site are
+## found, the rank values of those hits are reduced by shifting
+## them collapserankshift places to the right.
+collapserankshift int default=0
+
+## The maximum number of active and queued requests. Exceeding
+## requests will be discarded.
+cutofftransportconns int default=1024
+
+## specifies the directory where the dataset resides. Only the
+## directory needs to be specified. fsearch will then read the config
+## files (index.cf) in that directory to discover the structure.
+datasetdir string default=""
+
+## If set to "yes", then fallback to the default index if a query
+## term specifies a non-existing index. If set to "no", then always
+## return 0 hits for a query term that specifies a non-existing index.
+## Note that the result set for the entire query might still contain
+## hits from other query terms unless the invalid query term was an
+## and-term. Default value is "no".
+defaultindexfallback string default="no"
+
+# ???
+docattrhashsize int default=8171
+# ???
+docidhashsize int default=8171
+# ???
+docport int default=0
+
+## If present, the result cache is not flushed due to reload operation
+## unless the document summaries have changed.
+dontflushresultcacheonreload bool default=false
+
+## Colon-delimeted list of catalogs for which negative dictionary
+## entries should not be cached. Default is unset. Most useful for
+## dictionary files that are memorymaped or memorylocked in
+## indextune.cf. Note that the list must start and end with a colon (:).
+dropnegativedictionarycacheentriescatalogs string default=""
+
+## specifies a term (used in an ANDNOT) to be used for filtering ALL queries.
+excludefilter string default=""
+
+## If set to "yes", use of firstoccproximity is enabled. If set to
+## "no", use of firstoccproximity is disabled.
+firstoccproximity string default=""
+
+## If present, the filter occurrence cache is flushed due to reload
+## operation when all queries using the old configuration has completed.
+flushfilteroccscacheonreload bool default=false
+
+## Do not use position occurrence information, even though it might
+## be present in the index.
+forceemptyposoccs bool default=false
+
+## The port to run Fnet Remote Tools (RPC) service on.
+## If set to 0, no FRT service is provided.
+frtport int default=0
+
+## The directory where gid based blacklist files are found.
+## Used for "realtime" indexing setups.
+gidblacklistdir string default="../gidblacklist"
+
+## A semicolon-separated list of index names and index name prefixes
+## defining the set of indexes that are relevant when highlighting
+## query keywords. The terms and phrases from the query targeting any
+## of these indexes will be highlighted when dynamic teasers are
+## generated. In order to separate index names and index name prefixes
+## in the list, index name prefixes have a trailing '*'. Note that
+## index aliases are treated like actual index names. This means that
+## if you have an index relevant for highlighting and an index alias
+## pointing to that index, you need to configure both as relevant for
+## highlighting if you want to highlight keywords targeting both the
+## actual index and the alias. Example config value: "normal*;title".
+## Default config value: "*" (highlight all keywords).
+highlightindexes string default="*"
+
+# ???
+hostname string default=""
+
+## provide a HTTP server at the given port number.
+hport int default=8002
+
+## If true, the TCP_NODELAY option is set on the http connections.
+## This causes non-full packets to be sent even though previously sent
+## data hasn't yet been acknowledged (e.g. due to the delayed ack
+## feature present on various tcp stacks).
+httpdnodelay bool default=false
+
+# ???
+intoccpoolsize int default=32768
+# ???
+intoccpoolstep int default=32768
+# ???
+jobqueuethreads int default=5
+
+## Juniper configuration property map.
+## currently known keys with defaults:
+## [juniper.dynsum.highlight_on] = "<b>"
+## [juniper.dynsum.highlight_off] = "</b>"
+## [juniper.dynsum.continuation] = "..."
+## [juniper.dynsum.length] = "256"
+## [juniper.dynsum.min_length] = "128"
+## [juniper.stem.min_length] = "5"
+## [juniper.stem.max_extend] = "3"
+## [juniper.dynsum.surround_max] = "128"
+## [juniper.dynsum.max_matches] = "3"
+## [juniper.dynsum.escape_markup] = "auto"
+## [juniper.matcher.winsize] = "200"
+## [juniper.dynsum.separators] = "\0x1F\0x1D"
+## [juniper.dynsum.connectors] = "\0x1F\0x1D"
+## [juniper.proximity.factor] = "0.25"
+## [juniper.debug_mask] = "0"
+##
+#junipersetup properties
+
+## The maximum number of HTTP connections.
+maxhttpconns int default=1024
+
+## The maximum interval between a successful read from a socket
+## before timeout, in seconds.
+maxsocksilent double default=5.0
+
+## The maximum number of threads to use.
+maxthreads int default=100
+
+## The maximum number of active requests at any time. Exceeding
+## requests will be queued.
+maxtransportconns int default=15
+
+## If present the index to the document summary file (docsum.idx)
+## is accessed on disk on each access instead of being cached in
+## memory. For experimental use on systems with very many docu-
+## ments but very few actual docsum requests.
+nodocsumidxinmemory bool default=false
+
+## If set then locks on result cache are held only for very
+## short intervals and only a single cache element is locked at a
+## time. This simplifies the mutex locking order, but cause extra
+## load due to queries that would previously first block then use
+## cached value now being fully evaluted.
+nonblockingresultcache bool default=false
+
+## A boolean value controlling removal of several common accented
+## uses of characters, used when matching for highlighting.
+normalize.accentremoval bool default=true
+
+## A boolean value controlling normalizing of LATIN CAPITAL/SMALL
+## LIGATURE OE (U+0152 U+0153) to the string "oe", used when matching
+## for highlighting.
+normalize.ligaturesubstitution bool default=true
+
+## A boolean value controlling normalizing of various accented letters
+## to two chars, for linguistics compatibility.
+normalize.multicharexpansion bool default=true
+
+## A boolean value controlling normalizing of LATIN SMALL LETTER SHARP S
+## (U+00DF) to the string "ss", used when matching for highlighting.
+normalize.sharpssubstitution bool default=true
+
+## If true, queries are still handled during the reload operation
+## (even when the document summaries have changed). If false then
+## queries are stalled until reload has completed.
+overlappedreload bool default=true
+
+## The partition number to report to the connecting fdispatch process
+## if the dataset label didn't specify the partition number.
+partition int default=0
+
+## If set to "yes", use of proximity (cf. proximity and firstoccproximity)
+## will affect phrases in addition to single words. If set to "no",
+## use of proximity is never used for phrases.
+phraseproximity string default=""
+
+## The file name to write the PID of the fsearch process to.
+pidfile string default=""
+
+## Minimum value for maximum value of number of 'posocc' entries
+## for a word. If set to 0, computed as 2 * binlow.
+posbinhigh int default=0
+
+## Maximum value for maximum value of number of 'posocc' entries
+## for a word. If set to 0, computed as min(4 * binhigh, 0x7fffffff)
+posbinlow int default=0
+
+## The maximum value for number of 'posocc' entries for a word,
+## specified as a percentage of the number of documents in the index.
+## If more entries are needed for evaluation, posocc entries are not
+## used for that word and evaluation will be performed without full
+## proximity support. The percentage can be ramped off with the posbinlow
+## and posbinhigh variables. If set to 0, computed as 2.0 * binsize.
+posbinsize double default=0
+
+## If set to "yes", use of posocc files is enabled, except when
+## "forceemptyposoccs" is set or posocc files doesn't exist. If
+## set to "no", use of posocc files is disabled.
+proximity string default=""
+
+## Selects behavior when proximity can be used for two words but
+## not three words while firstoccproximity can be used for three
+## words. If set to "yes", then use proximity for two words. If
+## set to "no", then use firstoccproximity for three words.
+proximitypairbeforefirstoccproximitytriple string default=""
+
+## Selects behavior when proximity can be used for three words but
+## not four words while firstoccproximity can be used for four
+## words. If set to "yes", then use proximity for three words. If
+## set to "no", then use firstoccproximity for four words. The
+## default is "yes".
+proximitytriplebeforefirstoccproximityquad string default=""
+
+## specifies the port number for the persistent internal transport
+## protocol provided for a multi-level dispatch system.
+ptport int default=8003
+
+## a reference to a rank-profiles.def type configuration
+## describing how to rank results. If empty, fsearch loads
+## rank.cf from the dataset directory, see rank.cf(5).
+rankcf reference
+
+## specifies a lower limit for the rankvalue of the results
+## returned from the search node.
+rankcutoff int default=0
+
+## if set, an internal calculation is used for determining a
+## rank cutoff value as above.
+rankcutoffadvanced bool default=false
+
+## Specifies the constant value used in the internal advanced rank
+## cutoff calculations done when the rankcutoffadvanced parameter is set.
+## This roughly reflects the expected rank contribution of
+## one good term.
+rankcutoffadvval int default=0
+
+# ???
+rankinginfopoolsize int default=1048576
+# ???
+rankinginfopoolstep int default=262144
+## Grace period (in seconds) after index reload where old index is
+## still available.
+reloadgraceperiod int default=64
+
+## Maximum number of entries in the resultattributescache.
+resultattributescachequeries int default=0
+## Maximum number of entries in the resultattributescache.
+## 0 means no limitation.
+resultattributescachesize int default=5000000
+
+## The maximum lifetime of a resultcache element in seconds.
+resultcachemaximumlifetime int default=7200
+## The minimum lifetime of a resultcache element in seconds.
+resultcacheminimumlifetime int default=3600
+
+## Maximum number of entries in the resultcache.
+## 0 means no limitation.
+resultcachequeries int default=0
+## Maximum size (in bytes) in the resultcache.
+resultcachesize int default=50000000
+
+# ???
+rewriter.indexes string default=""
+# ???
+rewriter.langfield string default="bsumlanguage"
+# ???
+rewriter.rootdir string default=""
+# ???
+#rewritersetup properties
+
+## Set the number of samples a disk is marked as slow before
+## starting selftest when no progress occurred during the last
+## slowdisksamples samples.
+slowdisklatch int default=1
+
+## Set the number of milliseconds to sleep between each sample of
+## disk state.
+slowdisksamplemillisleep int default=100
+
+## Set the number of nanoseconds to sleep between each sample of
+## disk state. If 0, use slowdisksamplemillisleep instead.
+## Restriction: This option only has effect on FreeBSD
+## using the LinuxThreads port.
+slowdisksamplenanosleep int default=0
+
+## Set the number of contigous disk samples without progress and
+## outstanding requests before a disk is detected as slow.
+## If zero, automatic slowdisk detection is turned off.
+## Recommended value is '20' (when using default values for the
+## other slowdisk detection parameters).
+slowdisksamples int default=0
+
+# ???
+staticfilter string default=""
+
+## specifies a set of indexes for which to optimize exclusion
+## handling. Colon is used to separate index names. Default is unset.
+## The optimization use entries in the filter occurrence cache, thus
+## a too small size limit of the cache will cause serious performance
+## degradation.
+staticfilterindexes string default=""
+
+# ???
+strictbind bool default=false
+
+## Specifies a file containing url coded queries to run as part of self
+## test initiated when a slow disk has been detected.
+testloadfile string default=""
+
+## the type of transport to use. Currently only "fnet" is available.
+transport string default=""
+
+## Specifies the transport access log file used by the http server.
+## The real log file name is created by using the strftime function
+## with the given argument as template.
+transportaccesslog string default=""
+
+## Specifies the interval between transport access log rotation.
+## This is the number of minutes between log rotation, e.g a value
+## of 1440 indicates that the log should be rotated every 24 hour.
+transportaccesslogcycle int default=1440
+
+## Specifies the offset from the start of each cycle when the
+## transport access log should be cycled automatically. The unit
+## is seconds, e.g. a value of 1020 indicates that the log should
+## be cycled at 17 minutes past each cycle.
+transportaccesslogcycleoffset int default=0
+
+## Reference to VSM (Vespa Stream Matcher) configuration. If this is
+## set, fsearch will run in VSM mode.
+vsmconfig reference
+
+## If true, the TCP_NODELAY option is set on the persistent transport
+## connections. This causes non-full packets to be sent even though
+## previously sent data hasn't yet been acknowledged (e.g. due to the
+## delayed ack feature present on various tcp stacks).
+transportnodelay bool default=true
+
+# ???
+wordfolder bool default=false
+# ???
+wordhashsize int default=524269
+# ???
+wordoccpoolsize int default=2097152
+# ???
+wordoccpoolstep int default=524288
+# ???
+wordpoolsize int default=262144
+# ???
+wordpoolstep int default=65536
+
+## Connect spec for transactionlog server.
+tlsspec string default=""
+
+## Document manager config
+documentmanagerconfigid reference
+
+functionmodules[] string restart
+
+specialchars string
+
+tokenlist[].name string
+tokenlist[].tokens[].token string
+tokenlist[].tokens[].replace string default=""
+
+afloat double default=34 range=[0,1002]
+
+## qr-searchers:
+tag.bold.open string default="<hi>"
+tag.bold.close string default="</hi>"
+tag.separator string default="<sep />"
+
+## This array contains the built-in searchers that should
+## normally always run in a Vespa-S system. The actual list is
+## in the global/ directory on the configserver.
+builtin[].searcher string
+
+## If for some reason you need to disable one of the built-in
+## searchers you can set this flag to "false". Handle with great
+## care. You need to match the array index from the global/
+## directory, and this may change depending on versions.
+builtin[].enabled bool default=true
+
+# some searcher specific configuration parameters:
+
+com.yahoo.prelude.searcher.FieldCollapsingSearcher.collapsesize int default=1
+com.yahoo.prelude.searcher.FieldCollapsingSearcher.extrafactor double default=2.0
+com.yahoo.prelude.searcher.FieldCollapsingSearcher.collapsefield string default="mid"
+
+com.yahoo.prelude.searcher.BlendingSearcher.numthreads int default=200
+com.yahoo.prelude.searcher.BlendingSearcher.docid string default=""
+
+com.yahoo.prelude.searcher.BoldingSearcher.source string default=""
+
+com.yahoo.prelude.searcher.JuniperSearcher.source string default=""
+com.yahoo.prelude.searcher.JuniperSearcher.defaultdoctype string default=""
+
+## Query cache that can be placed anywhere in the search chain. Query/Result
+## pairs (ie. entries) bigger than maxentrysizebytes will not be cached.
+com.yahoo.prelude.searcher.CachingSearcher.cachesizemegabytes int default=100
+com.yahoo.prelude.searcher.CachingSearcher.timetoliveseconds int default=3600
+com.yahoo.prelude.searcher.CachingSearcher.maxentrysizebytes int default=10000
+
+com.yahoo.prelude.searcher.XMLStringSearcher.source string default=""
+
+## relevancy as measured from the backend will usually be
+## normalized into the [0,1000] range to make blending between
+## several backends with different relevancy models possible; you
+## can elect to skip this if you only have backends using
+## relevancy scores that are directly comparable.
+com.yahoo.prelude.fastsearch.FastSearcher.skipnormalizing bool default=true
+
+## how many aggregation groups to fetch from the backend
+com.yahoo.prelude.grouping.AggregatingSearcher.maxgroups int default=100
+
+com.yahoo.prelude.querytransform.PhrasingSearcher.automatonfile string default=""
+com.yahoo.prelude.querytransform.NonPhrasingSearcher.automatonfile string default=""
+com.yahoo.prelude.querytransform.TermReplacingSearcher.termlist[] string
+com.yahoo.prelude.querytransform.CompleteBoostSearcher.source string default=""
+
+com.yahoo.prelude.querytransform.ExactStringSearcher.source string default=""
+com.yahoo.prelude.querytransform.LiteralBoostSearcher.source string default=""
+com.yahoo.prelude.querytransform.TermBoostSearcher.source string default=""
+com.yahoo.prelude.querytransform.NormalizingSearcher.source string default=""
+com.yahoo.prelude.querytransform.StemmingSearcher.source string default=""
+
+com.yahoo.prelude.statistics.StatisticsSearcher.latencybucketsize int default=30
+
+
+# here users may add their custom searchers
+# (all strings should be class names)
+customizedsearchers.rawquery[] string
+customizedsearchers.transformedquery[] string
+customizedsearchers.blendedresult[] string
+customizedsearchers.unblendedresult[] string
+customizedsearchers.backend[] string
+customizedsearchers.argument[].key string
+customizedsearchers.argument[].value string
+
+## This is for adding searchers which should be below BlendingSearcher,
+## but not be linked to any Vespa cluster (directly).
+external[].name string
+external[].searcher[] string
+
+# Search cluster specific information.
+## Name of search cluster.
+searchcluster[].name string default=""
+
+## Names of search definitions served by search cluster.
+searchcluster[].searchdef[] string
+
+## configid that may be used to get rank-profiles config for the cluster.
+searchcluster[].rankprofiles.configid reference default=""
+
+## Indexing mode of search cluster.
+searchcluster[].indexingmode enum { REALTIME, STREAMING } default=REALTIME
+
+## Storage cluster to use for search cluster if indexingmode is streaming.
+searchcluster[].storagecluster string default=""
+
+# The available dispatchers on each search cluster
+searchcluster[].dispatcher[].host string
+searchcluster[].dispatcher[].port int
+
+## The number of least significant bits of the part id used to specify the
+## row number (the rest of the bits specifies the column). Don't touch
+## this unless you know why you are doing it.
+searchcluster[].rowbits int default=0
+
+
+# 4 search-cluster-specific overrides of global cache parameters:
+
+## Internal searcher cache. Size is measured in megabytes of raw packet
+## size. Hits larger than 1% of total cache size will not be cached.
+searchcluster[].cache.size int default=1
+
+## Timeout for internal searcher cache. Entries older than this number
+## of seconds will be removed from cache. 0 means no cache timeout.
+## If cachetimeoutseconds is used, the cache is not purged when reindexing.
+searchcluster[].cache.timeout int default=-1
+
+## If timeoutwithpurging is set, the index will be purged (possibly
+## gradually) when index switching occurs, even if cache timeout > 0.
+## Not used if cache timeout is 0. If searchcluster[].cache.timeout is
+## not explicitly set, the global value will be used instead of the one
+## set locally.
+searchcluster[].cache.timeoutwithpurging bool default=false
+
+## Gradual cache switching causes the index to only be gradually purged
+## when cache switching occurs. cacheswitchseconds is the length of the period
+## when a given cache entry may be from either the previous or current
+## index. Setting it to 0 makes QRS purges the cache entirely when a new
+## index becomes available. At the end of the cacheswitchseconds period,
+## the cache will be cleaned of any remaining entries from the previous
+## index.
+searchcluster[].cache.switchseconds int default=-1
+
+# Per dispatcher config-id might be nice to have, remove it until needed.
+# searchcluster[].dispatcher[].configid reference
+
+# rank-profiles
+## name of this rank profile. maps to table index for internal use.
+rankprofile[].name string
+
+## the name of a generic property available to the feature execution framework and feature plugins
+rankprofile[].fef.property[].name string
+
+## the value of a generic property available to feature plugins
+rankprofile[].fef.property[].value string
+
+## the catalog name overrides apply to
+rankprofile[].catalog[].name string
+
+## Boost value for AND queries in this catalog.
+rankprofile[].catalog[].andboost int default=0
+
+## Boost value for OR queries in this catalog.
+rankprofile[].catalog[].orboost int default=0
+
+## Boost value for ANY queries in this catalog.
+rankprofile[].catalog[].anyboost int default=0
+
+## Boost value for NEAR queries in catalog.
+rankprofile[].catalog[].nearboost int default=0
+
+## Boost value for ORDEREDNEAR queries in this catalog.
+rankprofile[].catalog[].orderednearboost int default=0
+
+## Boost value for phrase queries in this catalog.
+rankprofile[].catalog[].phraseboost int default=0
+
+## Boost value for all queries in catalog.
+rankprofile[].catalog[].rankboost int default=0
+
+## If true, the context boost is the max value of
+## the individual contextboosts.
+## When false, the context boost when a term is in
+## several contexts is the sum of the individual contextboosts.
+rankprofile[].catalog[].bestcontextboostonly bool default=false
+
+
+## If true, then use extnumoccboost only when calculating rank values.
+## Also, do not normalize the extnumoccboost value with
+## global term frequency. Default value is false.
+rankprofile[].catalog[].extnumoccboostonly bool default=false
+
+## If yes, then use extnumoccboost only when calculating rank values.
+## Also, do not normalize the extnumoccboost value with
+## global term frequency. Default value is no.
+rankprofile[].catalog[].numoccandextnumoccboostonly bool default=false
+
+## If yes, then use bitvectors when possible.
+## Default value is false.
+rankprofile[].catalog[].preferbitvector bool default=false
+
+## Load extnumoccboost for this catalog from the named file.
+## extnumoccboost specifies boost values due to the number of
+## occurences of a term that are external to the document. If
+## "NULL" is given as file name, then all extnumoccboost values
+## will be set to 0.
+rankprofile[].catalog[].extnumoccboost.table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load numoccboost for this catalog from the named file.
+## numoccboost specifies boost values due to the number of occurences in
+## a document. If "NULL" is given as file name, then all numoccboost
+## values will be set to 0.
+rankprofile[].catalog[].numoccboost.table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load firstoccboost for catalog from the file named.
+## firstoccboost specifies boost values due to the position of the
+## first occurence in a document. If "NULL" is given as file name,
+## then all firstoccboost values will be set to 0.
+rankprofile[].catalog[].firstoccboost.table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+
+## Load firstoccproximityboost for this catalog from the file named.
+## firstoccproximity boost specifies boost values due to the correlation between
+## positions of the first occurence in a document for two and two words.
+##
+## If "NULL" is given as file name, then all
+## firstoccproximityboost values will be set to 0. If otherwise set,
+## should be the name of a file to load into the table. The file
+## should have 256 lines each containing a single integer.
+##
+## There are 256 elements in the table, handling forward distances from 1.
+## The corresponding firstoccrevproximityboost table is used
+## to handle closeness in reverse order.
+##
+## The last array index specifies the proximity table set. During
+## evaluation, the bigram proximity weight supplied by the query segmenter
+## specifies which proximity table set to use, with a fallback to set 0
+## when no information is available.
+rankprofile[].catalog[].firstoccproximityboost[].table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load firstoccrevproximityboost table for this catalog from the named file.
+## Specifies boost values due to the correlation between positions
+## of the first occurence in a document for two and two words when
+## the second word in the query comes first in the document.
+## See also firstoccproximityboost above.
+rankprofile[].catalog[].firstoccrevproximityboost[].table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load proximityboost for this catalog from the named file.
+## proximity boost specifies boost values due to the correlation between
+## positions of the occurences in a document for two and two words.
+## See also firstoccproximityboost above.
+rankprofile[].catalog[].proximityboost[].table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load revproximityboost for this catalog from the named file.
+## revproximity boost specifies boost values due to the correlation between
+## positions of the occurences in a document for two and two words.
+## See also firstoccproximityboost above.
+rankprofile[].catalog[].revproximityboost[].table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## Load divtable for this catalog from the named file.
+## Rank values for a query term are divided by the entry
+## in divtable indexed by log2 of term frequence.
+## The file should contain ?? lines each with a single integer.
+rankprofile[].catalog[].divtable string default=""
+
+## The name of a context in this catalog to specify boosts for.
+rankprofile[].catalog[].context[].name string
+
+## Boost occurrences in this context with the given value.
+## XXX -1 uses default (???) from somewhere(TM).
+rankprofile[].catalog[].context[].contextboost int default=0
+
+## Boost pair of occurrences in this context with
+## the given value when evaluating 2 words from same catalog in
+## parallell.
+## XXX -1 uses default (???) from somewhere(TM).
+rankprofile[].catalog[].context[].commoncontextboost.pair int default=0
+
+## Boost triple of occurrences in this context with
+## the given value when evaluating 3 words from same catalog in
+## parallell.
+## XXX -1 uses default (???) from somewhere(TM).
+rankprofile[].catalog[].context[].commoncontextboost.triple int default=0
+
+## Boost quad of occurrences in this context with
+## the given value when evaluating 4 words from same catalog in
+## parallell.
+## XXX -1 uses default (???) from somewhere(TM).
+rankprofile[].catalog[].context[].commoncontextboost.quad int default=0
+
+
+## The name of the attribute
+rankprofile[].attribute[].name string
+
+## Boost value for queries that hit in this attribute
+rankprofile[].attribute[].attributecontextboost int default=0
+
+## Load weightboost for this attribute from the named file.
+## weightboost specifies boost values due to the weight (weighted set)
+## or number of occurences (single, array) in an attribute.
+## If "NULL" is given as file name, then all weightboost values will be set to 0.
+rankprofile[].attribute[].weightboost.table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+
+## Load static rank values from the given staticrank docattr vector.
+## Must be specified in index.cf as a staticrankfile.
+rankprofile[].staticrankfile string default=""
+
+## Multiply static rank values with given value when calculating total
+## rank value.
+rankprofile[].staticcoefficient int default=1
+
+## If false then use only static ranking when sorting result hits.
+## Default is true.
+rankprofile[].dynamicranking bool default=true
+
+## If dynamic ranking is turned off, then ascending will sort the
+## result hits with lowest static rank values first, while
+## descending will sort with highest static rank values
+## first. Default is descending. This keyword has no effect if
+## dynamic ranking is on.
+rankprofile[].staticranksortorder string default="descending"
+
+## Load static rank mapping from the file named table. The static
+## rank mapping maps each 8-bit static rank value into a 32-bit static
+## rank value. This option may only be used with 8-bit static rank files.
+rankprofile[].staticrankmap string default=""
+
+## If set to "true", total rank will be reduced when dynamic rank is less than
+## 25% of static rank, to suppress irrelevant hits from popular sites.
+## If set to "false", total rank is not reduced.
+rankprofile[].clampstaticrank bool default=false
+
+## Load document datetime values used for freshness boost calculation from
+## this file. The values must be coded as minutes since
+## 1900-01-01T00:00Z. The value 0 has the special meaning
+## "no datetime value exists".
+rankprofile[].freshnessboost.file string default=""
+
+## Load freshnessboost lookup-table values from the file named
+## table instead of using built-in default values. The file must
+## contain 32 white-space separated non-negative integers.
+rankprofile[].freshnessboost.table string default="/home/vespa/conf/vespa/search/ranktables/constant-0000"
+
+## When calculating the freshness boost value multiply difference between
+## current datetime and document datetime with timeoffset before taking
+## the base-2 logarithm. Default value is 1. Max value is 31.
+rankprofile[].freshnessboost.timeoffset int default=1
+
+## If a document has datetime value 0, then use defaultboostvalue
+## as freshness boost value instead of doing table lookup. The default
+## default value is 0 (no boost).
+rankprofile[].freshnessboost.defaultboostvalue int default=0
+
+## Multiply freshness boost value with coefficient when calculating
+## total freshness boost value. If coefficient 0 is used, no freshness
+## boost value will be computed or added. Default value is 0.
+rankprofile[].freshnessboost.coefficient int default=0
+
+## boost table files for distance ranking, 1 dimension.
+## The tables have 465 elements each, where slots 0..15 represents
+## distances 0..15 while the remaining slots represents distance
+## (16 + (slot & 15)) << ((slot >> 4) - 1). Linear interpolation is
+## used for distances "between" table slots.
+##
+## If "NULL" is given as the file name then all 1D distance boost values
+## for that table will be set to 0.
+rankprofile[].distance1dboosttable[].table string
+
+## boost table files for distance ranking, 2 dimensions.
+## The tables have 977 elements each, where slots 0..15 represents
+## square of distance being 0..15 while the remaining slots represents
+## square of distance distance being
+## (16 + (slot & 15)) << ((slot >> 4) - 1). Linear interpolation is
+## used for distances "between" table slots.
+##
+## If "NULL" is given as the file name then all 2D distance boost values
+## for that table will be set to 0.
+rankprofile[].distance2dboosttable[].table string
+
+## The lowest possible size of a ranked result. This is the lower ramp
+## of the percentage specified in the binsize variable. The default is
+## specified in fsearchrc.
+rankprofile[].binlow int default=-1
+
+## The high limit of the ranked result bin. If the percentage of the
+## resultset specified in binsize is higher than this limit, this will be
+## the max size. The default is specified in fsearchrc.
+rankprofile[].binhigh int default=-1
+
+## The size of the ranked results as a percentage of the total result
+## set size. The percentage can be ramped off with the binlow and binhigh
+## variables. The default is specified in fsearchrc.
+rankprofile[].binsize double default=-1
+
+## Minimum value for maximum value of number of 'posocc' entries for a word.
+## The default is specified in fsearchrc.
+rankprofile[].posbinlow int default=-1
+
+## Maximum value for maximum value of number of 'posocc' entries for a word.
+## The default is specified in fsearchrc.
+rankprofile[].posbinhigh int default=-1
+
+## The maximum value for number of 'posocc' entries for a word, specified
+## as a percentage of the number of documents in the index. If more
+## entries are needed for evaluation, posocc entries are not used for that
+## word and evaluation will be performed without full proximity support.
+## The percentage can be ramped off with the posbinlow and posbinhigh
+## variables. The default is specified in fsearchrc.
+rankprofile[].posbinsize int default=-1
+
+## After all other rank calculations, the rank value is tuned according
+## to the tunefactor and tunebias values. The rank value is modified
+## as follows: new_rank = old_rank * tunefactor + tunebias.
+rankprofile[].tunefactor double default=1.0
+
+## After all other rank calculations, the rank value is tuned according
+## to the tunefactor and tunebias values. The rank value is modified
+## as follows: new_rank = old_rank * tunefactor + tunebias.
+rankprofile[].tunebias int default=0
+
+## A lower limit for the rankvalue of the results returned from the
+## search node. If rankcutoff.advanced is set to "true", determines
+## the constant value used in the internal advanced rank cutoff
+## calculations. This roughly reflects the expected rank contribution
+## of one good term.
+## The rankcutoff.val value and the rankcutoff.advanced parameter
+## may be used if you only want hits with a minimum relevancy to show
+## up in the resultset.
+## A value below zero means no rankcutoff is done.
+rankprofile[].rankcutoff.val int default=-1
+
+## When rankcutoff.val is in use, this flag controls whether to use
+## an internal calculation is used for determining the rank cutoff
+## value. If "false", use rankcutoff.val as a direct lower limit.
+rankprofile[].rankcutoff.advanced bool default=false
+
+## If set to "ON", use of posocc files is enabled, except when
+## "forceemptyposoccs" is set in fsearchrc or posocc files doesn't exist.
+## If set to "OFF", use of posocc files is disabled.
+## If "NOTSET" the fsearchrc "proximity" parameter is used instead.
+rankprofile[].proximity.full.enable enum { OFF, ON, NOTSET } default=NOTSET
+
+## If set to "ON", use of firstoccproximity is enabled.
+## If set to "OFF", use of firstoccproximity is disabled.
+## When NOTSET use the firstoccproximity value in fsearchrc configuration.
+rankprofile[].proximity.firstocc.enable enum { OFF, ON, NOTSET } default=NOTSET
+
+## If set to "ON", use of proximity (cf. proximity and firstoccproximity)
+## will affect phrases in addition to single words.
+## If set to "OFF", proximity is never used for phrases.
+## When NOTSET use the phraseproximity value in fsearchrc configuration.
+rankprofile[].proximity.phrase.enable enum { OFF, ON, NOTSET } default=NOTSET
+
+## Selects behavior when proximity can be used for two words but not three
+## words while firstoccproximity can be used for three words.
+## If set to "ON", then use proximity for two words.
+## If set to "OFF", then use firstoccproximity for three words.
+## When NOTSET use the proximitypairbeforefirstoccproximitytriple value
+## in fsearchrc configuration.
+rankprofile[].proximity.pairbeforefirstocctriple.enable enum { OFF, ON, NOTSET } default=NOTSET
+
+## Selects behavior when proximity can be used for three words but not four
+## words while firstoccproximity can be used for four words.
+## If set to "ON", then use proximity for three words.
+## If set to "OFF", then use firstoccproximity for four words.
+## When NOTSET use the proximitytriplebeforefirstoccproximityquad value
+rankprofile[].proximity.triplebeforefirstoccquad.enable enum { OFF, ON, NOTSET } default=NOTSET
diff --git a/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-logging.cfg b/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-logging.cfg
new file mode 100755
index 00000000000..cc640619e3a
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-logging.cfg
@@ -0,0 +1,44 @@
+logger com.yahoo
+speciallog[6]
+speciallog[0].name QueryAccessLog
+speciallog[0].type file
+speciallog[0].filehandler.name QueryAccessLog
+speciallog[0].filehandler.pattern logs/vespa/qrs/QueryAccessLog.%Y%m%d%H%M%S
+speciallog[0].filehandler.rotation "0 60 ..."
+speciallog[0].cachehandler.name QueryAccessLog
+speciallog[0].cachehandler.size 1000
+speciallog[1].name QueryResultLog
+speciallog[1].type cache
+speciallog[1].filehandler.name QueryResultLog
+speciallog[1].filehandler.pattern logs/vespa/qrs/QueryResultLog.%Y%m%d%H%M%S
+speciallog[1].filehandler.rotation "0 60 ..."
+speciallog[1].cachehandler.name QueryResultLog
+speciallog[1].cachehandler.size 1000
+speciallog[2].name ResultImpressionLog
+speciallog[2].type file
+speciallog[2].filehandler.name ResultImpressionLog
+speciallog[2].filehandler.pattern logs/vespa/qrs/ResultImpressionLog.%Y%m%d%H%M%S
+speciallog[2].filehandler.rotation "0 60 ..."
+speciallog[2].cachehandler.name ResultImpressionLog
+speciallog[2].cachehandler.size 1000
+speciallog[3].name ServiceEventLog
+speciallog[3].type cache
+speciallog[3].filehandler.name ServiceEventLog
+speciallog[3].filehandler.pattern logs/vespa/qrs/ServiceEventLog.%Y%m%d%H%M%S
+speciallog[3].filehandler.rotation "0 60 ..."
+speciallog[3].cachehandler.name ServiceEventLog
+speciallog[3].cachehandler.size 1000
+speciallog[4].name ServiceStatusLog
+speciallog[4].type off
+speciallog[4].filehandler.name ServiceStatusLog
+speciallog[4].filehandler.pattern logs/vespa/qrs/ServiceStatusLog.%Y%m%d%H%M%S
+speciallog[4].filehandler.rotation "0 60 ..."
+speciallog[4].cachehandler.name ServiceStatusLog
+speciallog[4].cachehandler.size 1000
+speciallog[5].name ServiceTraceLog
+speciallog[5].type parent
+speciallog[5].filehandler.name ServiceTraceLog
+speciallog[5].filehandler.pattern logs/vespa/qrs/ServiceTraceLog.%Y%m%d%H%M%S
+speciallog[5].filehandler.rotation "0 60 ..."
+speciallog[5].cachehandler.name ServiceTraceLog
+speciallog[5].cachehandler.size 1000
diff --git a/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-templates.3.cfg b/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-templates.3.cfg
new file mode 100644
index 00000000000..345c20157f9
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/configsglobal/qr-templates.3.cfg
@@ -0,0 +1,111 @@
+washing -0.45
+washer 0
+hey[2]
+hey[0].ho[1]
+hey[0].ho[0].lets[3]
+hey[0].ho[0].lets[0].go "slayer"
+hey[0].ho[0].lets[0].fishing -1
+hey[0].ho[0].lets[0].ref JA
+hey[0].ho[0].lets[1].go "gate"
+hey[0].ho[0].lets[1].ref :parent:
+hey[0].ho[0].lets[str].go "strung"
+hey[0].ho[0].me 0.0
+hey[fooo].ho[0]
+
+hey[0] 78
+
+longVal 500
+longWithRange -9000000000
+longArr[2]
+longArr[0] 0
+longArr[1] 1
+longArrWithRange[1]
+longArrWithRange[0] 9000000000
+
+bar.arrline[0] "foo"
+bar.arrline[1] "bar"
+
+fileVal "/nodefault/file"
+fileArr[2]
+fileArr[0] "keyb.com"
+fileArr[1] "2b4a64d0cb36d44ba8a52506d9fe480bf3511be6"
+
+urlprefix "foo"
+templateset[2]
+templateset[0].urlprefix "/basic"
+#templateset[0].mimetype "text/xml"
+templateset[0].encoding "ut\"f-8\n' <hit relevancy=\"$relevancy\">\n#foreach"
+templateset[0].headertemplate "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<resultset totalhits=\"$result.hitCount\">\n"
+templateset[0].footertemplate "</resultset>\n"
+templateset[0].nohitstemplate "<empty/>\n"
+templateset[0].hittemplate "<hit relevancy=\"$relevancy\">\n#foreach( $key in $hit.getPropertyKeySet() )\n <field name='$key'>$hit.getPropertyXML($key)</field>\n#end\n</hit>\n"
+templateset[0].errortemplate "<ERROR CODE=\"$result.error.code\">$result.error.message</ERROR>\n"
+templateset[1].urlprefix "/xsearch"
+templateset[1].mimetype "text/xml"
+templateset[1].encoding "utf-8"
+templateset[1].rankprofile 0
+templateset[1].headertemplate "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<RESULTSET TOTALHITS=\"$result.hitCount\">\n"
+templateset[1].footertemplate "</RESULTSET>\n"
+templateset[1].nohitstemplate "0.56"
+templateset[1].hittemplate "<HIT RELEVANCY=\"$relevancy\" TYPE=\"$hit.typeString\">\n<FIELD NAME=\"uri\">$uri</FIELD>\n<FIELD NAME=\"category\">$category</FIELD>\n<FIELD NAME=\"bsumtitle\">$bsumtitle</FIELD>\n</HIT>\n"
+templateset[1].errortemplate "45"
+config[0].id :parent:
+config[0].autostart
+hi[0].there[0].e BATCH
+ilscript[music].name music
+ilscript[music].doctype music
+ilscript[music].content[1]
+ilscript[music].content[0] "\"music\" | summary sddocname | lowercase | index sddocname;"
+
+musum *
+
+auran "value=\"Confirm\"/></form>\"\"Tuna - step three."
+
+route[1]
+route[0].name "search/cluster.books2"
+route[0].selector "books.isbn=\"none\""
+route[0].feed "books"
+
+languages[3]
+languages[2] "swahili"
+
+languages2[2]
+languages2[0] "swedish"
+
+foolang[5]
+foolang[0].lang[3]
+foolang[0].lang[1] "Swahili"
+foolang[1].lang[3]
+foolang[2].lang[3]
+foolang[2].lang[0] "Setswana"
+foolang[3].lang[4]
+foolang[3].lang[3] "Norwegian"
+foolang[4].lang[2]
+
+myIntMap{"foo"} 67
+myIntMap{"bar"} 68
+myStringMap{"fo"} "gh"
+myStringMap{"ba"} "ij"
+myStructMap{"FOO"}.myInt 78
+myStructMap{"FOO"}.myString "myFoo"
+myStructMap{"FOOO"}.myInt 89
+myStructMap{"FOOO"}.myString "myFooS"
+myStructMap{"FOOO"}.myIntDef -99
+myStructMap{"FOOO"}.myStringDef "myFooSBall"
+
+myStructMap{"FOO"}.myStruct.a "guitar"
+myStructMap{"FOO"}.myStruct.c /tmp
+
+myStructMap{"FOOO"}.myStruct.a "bass"
+myStructMap{"FOOO"}.myStruct.b "drums"
+myStructMap{"FOOO"}.myStruct.c /var/log
+
+myStructMap{"FOO"}.myStructNestedArray[0] -9
+myStructMap{"FOO"}.myStructNestedArray[1] -10
+
+myStructMap{"FOO"}.myNestedLeafMap{"Nested1"} 90
+myStructMap{"FOO"}.myNestedLeafMap{"Nested2"} 9000
+
+myStructMap{"FOO"}.myNestedMap{"Nested3"}.myLong 809
+myStructMap{"FOO"}.myNestedMap{"Nested3"}.myLongDef 810
+myStructMap{"FOO"}.myNestedMap{"Nested4"}.myLong 811
diff --git a/config/src/test/java/com/yahoo/vespa/config/configsglobal/testfoobar.12.cfg b/config/src/test/java/com/yahoo/vespa/config/configsglobal/testfoobar.12.cfg
new file mode 100644
index 00000000000..3d23f7fd29a
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/configsglobal/testfoobar.12.cfg
@@ -0,0 +1,105 @@
+vh[5]
+vh[0] 0.3345
+vh[1] -0.3
+vh[2] 134.3
+vh[3] 234.34
+vh[4] -24.3
+bg[5]
+bg[0] 5
+bg[1]
+bg[2] 10
+bg[3] 4
+bg[4] -2
+gee[2]
+gee[0] "jabbaTuna"
+gee[1] jabba:500/dusteri
+storage[2]
+storage[0].distributor[2]
+storage[0].distributor[0] "tra"
+storage[0].distributor[1] "de"
+storage[0].feeder[1]
+storage[0].feeder[0] "li"
+#storage[0].feeder[1] "dum"
+storage[1].distributor[3]
+storage[1].distributor[0] "Etra"
+storage[1].distributor[1] "Ede"
+#storage[1].feeder[3]
+storage[1].feeder[0] "Eli"
+storage[1].feeder[1] "Edum"
+storage[1].feeder[2] "TEdum"
+
+#ju[].hu[].tu[] double default=45 range=[0,100.1]
+#ju[].hu[].wu[] enum { HEY, HO} default=HO
+#ju[].hu[].tang[] bool default=false
+#ju[].hu[].clan[] int default=45 range=[-90,90]
+
+ju[2]
+ju[0].hu[2]
+ju[0].hu[0].tu[2]
+ju[0].hu[0].tu[0] 45.0
+ju[0].hu[0].tu[1] 0.0
+ju[0].hu[0].tang[2]
+ju[0].hu[0].tang[0] true
+#ju[0].hu[0].tang[1] 0.0
+
+#ju[0].hu[1].tu[2]
+ju[0].hu[1].tu[0] 667.865
+ju[0].hu[0].wu[2]
+ju[0].hu[0].wu[0] HEY
+ju[0].hu[0].wu[1] HEY
+ju[0].hu[1].wu[1]
+ju[0].hu[1].wu[0] HO
+
+ju[1].hu[2]
+ju[1].hu[0].tu[2]
+ju[1].hu[0].tu[0] 78
+ju[1].hu[0].tu[1] 78.9
+ju[1].hu[1].tu[1]
+ju[1].hu[1].tu[0] 88.9
+
+ju[1].hu[0].wu[3]
+ju[1].hu[0].wu[0] HEY
+ju[1].hu[0].wu[1] HEY
+ju[1].hu[0].wu[2] HO
+ju[1].hu[1].wu[1]
+ju[1].hu[1].wu[0] HO
+ju[1].hu[1].clan[4]
+ju[1].hu[1].clan[0] 5
+ju[1].hu[1].clan[1] 6
+ju[1].hu[1].clan[2] 7
+ju[1].hu[1].clan[3] 8
+foo "123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa gh:sann\" da"
+headertemplate "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n<head>\n<title>ranqualizer:$hostname:$profile:</title>\n<link type=\"text/css\" rel=\"stylesheet\" href=\"/layout.css\"/>\n<meta name=\"ROBOTS\" content=\"NOINDEX,NOFOLLOW\"/><meta http-equiv=\"Cache-control\" content=\"no-cache\"/><meta http-equiv=\"Cache-control\" content=\"must-revalidate\"/><meta http-equiv=\"Cache-control\" content=\"max-age=0\"/><meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"/><link rel=\"icon\" href=\"/favicon.ico\"/><script type=\"text/javascript\">function show(foo,f){document.getElementById(foo).style.display=\"block\";} function hide(foo,f){document.getElementById(foo).style.display = \"none\";}</script></head>\n<body>\n"
+ptport 10108
+rankcf :parent:
+hport 10109
+frtport 10107
+transportnodelay true
+partition 0
+datasetdir none
+proximity "yes"
+vsmconfig ""
+documentmanagerconfigid :parent:
+include: search/cluster.music
+include: search/cluster.music2
+
+rankprofile[extra].fef.property[4]
+rankprofile[ignore].fef.property[4].name "vespa.dump.ignoredefaultfeatures"
+rankprofile[ignore].fef.property[4].value true
+
+specialchars "索 索尼 DVD±R"
+
+tokenlist[1]
+tokenlist[0].name "default"
+tokenlist[0].tokens[8]
+tokenlist[0].tokens[0].token "c++"
+tokenlist[0].tokens[1].token "wal-mart"
+tokenlist[0].tokens[1].replace "walmart"
+tokenlist[0].tokens[2].token ".net"
+tokenlist[0].tokens[3].token "-索"
+tokenlist[0].tokens[4].token "sony"
+tokenlist[0].tokens[4].replace "索尼"
+tokenlist[0].tokens[5].token "dvd+-r"
+tokenlist[0].tokens[6].token "DVD±R"
+tokenlist[0].tokens[7].token "dvdplusminusr"
+tokenlist[0].tokens[7].replace "dvd+-r"
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/ConfigResponseTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/ConfigResponseTest.java
new file mode 100644
index 00000000000..e39c0a6f623
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/ConfigResponseTest.java
@@ -0,0 +1,66 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.LZ4PayloadCompressor;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigResponseTest {
+
+ @Test
+ public void require_that_slime_response_is_initialized() throws IOException {
+ ConfigPayload configPayload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ InnerCNode targetDef = dParser.getTree();
+ ConfigResponse response = SlimeConfigResponse.fromConfigPayload(configPayload, targetDef, 3, "mymd5");
+ List<String> payload = response.getLegacyPayload();
+ assertNotNull(payload);
+ assertThat(payload.size(), is(6));
+ assertThat(payload.get(0), is("boolval false"));
+ assertThat(response.getGeneration(), is(3l));
+ assertThat(response.getConfigMd5(), is("mymd5"));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.serialize(baos, CompressionType.UNCOMPRESSED);
+ assertThat(baos.toString(), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+
+ @Test
+ public void require_that_slime_response_decompresses_on_serialize() throws IOException {
+
+ ConfigPayload configPayload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ InnerCNode targetDef = dParser.getTree();
+ Utf8Array data = configPayload.toUtf8Array(true);
+ Utf8Array bytes = new Utf8Array(new LZ4PayloadCompressor().compress(data.getBytes()));
+ ConfigResponse response = new SlimeConfigResponse(bytes, targetDef, 3, "mymd5", CompressionInfo.create(CompressionType.LZ4, data.getByteLength()));
+ List<String> payload = response.getLegacyPayload();
+ assertNotNull(payload);
+ assertThat(payload.size(), is(6));
+ assertThat(payload.get(0), is("boolval false"));
+ assertThat(response.getGeneration(), is(3l));
+ assertThat(response.getConfigMd5(), is("mymd5"));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.serialize(baos, CompressionType.UNCOMPRESSED);
+ assertThat(baos.toString(), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestBase.java b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestBase.java
new file mode 100644
index 00000000000..0cf6491432d
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestBase.java
@@ -0,0 +1,286 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.config.subscription.ConfigSet;
+import com.yahoo.config.subscription.ConfigSourceSet;
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.config.subscription.impl.GenericConfigSubscriber;
+import com.yahoo.config.subscription.impl.JRTConfigRequester;
+import com.yahoo.config.subscription.impl.JRTConfigSubscription;
+import com.yahoo.config.subscription.impl.MockConnection;
+import com.yahoo.jrt.Request;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.JsonDecoder;
+import com.yahoo.slime.Slime;
+import com.yahoo.test.ManualClock;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.ErrorCode;
+import com.yahoo.vespa.config.RawConfig;
+import com.yahoo.vespa.config.TimingValues;
+import com.yahoo.vespa.config.util.ConfigUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.3
+ */
+public abstract class JRTConfigRequestBase {
+
+ protected String defName = "mydef";
+ protected String defNamespace = "my.name.space";
+ protected String hostname = "myhost";
+ protected String configId = "config/id";
+ protected String defMd5 = "595f44fec1e92a71d3e9e77456ba80d1";
+ protected long currentGeneration = 3;
+ protected final Optional<VespaVersion> vespaVersion = Optional.of(VespaVersion.fromString("5.38.24"));
+ protected long timeout = 5000;
+ protected Trace trace ;
+ protected String configMd5 = ConfigUtils.getMd5(createPayload().getData());
+ protected JRTClientConfigRequest clientReq;
+ protected JRTServerConfigRequest serverReq;
+
+ @Before
+ public void setupRequest() throws IOException {
+ clientReq = createReq();
+ serverReq = createReq(clientReq.getRequest());
+ assertTrue(serverReq.validateParameters());
+ }
+
+ private JRTClientConfigRequest createReq() throws IOException {
+ trace = Trace.createNew(3, new ManualClock());
+ trace.trace(1, "hei");
+ return createReq(defName, defNamespace, defMd5, hostname, configId, configMd5, currentGeneration, timeout, trace);
+ }
+
+ private JRTClientConfigRequest createReq(Payload payload) throws IOException {
+ trace = Trace.createNew(3, new ManualClock());
+ trace.trace(1, "hei");
+ return createReq(defName, defNamespace, defMd5, hostname, configId, ConfigUtils.getMd5(payload.getData()), currentGeneration, timeout, trace);
+ }
+
+ protected abstract JRTClientConfigRequest createReq(String defName, String defNamespace, String defMd5, String hostname, String configId, String configMd5, long currentGeneration, long timeout, Trace trace) throws IOException;
+ protected abstract JRTServerConfigRequest createReq(Request request);
+ protected abstract JRTClientConfigRequest createReq(JRTConfigSubscription<SimpletypesConfig> sub, Trace aNew);
+ protected abstract JRTClientConfigRequest createFromRaw(RawConfig rawConfig, long serverTimeout, Trace aNew);
+
+ protected void request_is_parsed_base() {
+ String [] expectedContent = new String[]{
+ "namespace=my.name.space",
+ "myfield string"
+ };
+ System.out.println(serverReq.toString());
+ assertThat(serverReq.getConfigKey().getName(), is(defName));
+ assertThat(serverReq.getConfigKey().getNamespace(), is(defNamespace));
+ assertThat(serverReq.getConfigKey().getMd5(), is(defMd5));
+ assertThat(serverReq.getConfigKey().getConfigId(), is(configId));
+ assertThat(serverReq.getDefContent().asStringArray(), is(expectedContent));
+ assertFalse(serverReq.noCache());
+ assertTrue(serverReq.getRequestTrace().toString().contains("hi"));
+ assertThat(serverReq.getRequestConfigMd5(), is(configMd5));
+ assertThat(serverReq.getRequestGeneration(), is(currentGeneration));
+ }
+
+ @Test
+ public void delay_mechanisms_functions() {
+ assertFalse(serverReq.isDelayedResponse());
+ serverReq.setDelayedResponse(true);
+ assertTrue(serverReq.isDelayedResponse());
+ serverReq.setDelayedResponse(false);
+ assertFalse(serverReq.isDelayedResponse());
+ }
+
+ public JRTServerConfigRequest next_request_is_correct_base() {
+ String [] expectedContent = new String[]{
+ "namespace=my.name.space",
+ "myfield string"
+ };
+ JRTServerConfigRequest next = createReq(clientReq.nextRequest(6).getRequest());
+ assertThat(next.getConfigKey().getName(), is(defName));
+ assertThat(next.getConfigKey().getNamespace(), is(defNamespace));
+ assertThat(next.getConfigKey().getMd5(), is(defMd5));
+ assertThat(next.getConfigKey().getConfigId(), is(configId));
+ assertThat(next.getDefContent().asStringArray(), is(expectedContent));
+ assertFalse(next.noCache());
+ assertThat(next.getTimeout(), is(6l));
+ assertThat(next.getTimeout(), is(6l));
+ return next;
+ }
+
+
+ @Test
+ public void next_request_when_error_is_correct() {
+ serverReq.addOkResponse(createPayload(), 999999, "newmd5");
+ serverReq.addErrorResponse(ErrorCode.OUTDATED_CONFIG, "error message");
+ System.out.println(serverReq);
+ JRTClientConfigRequest next = clientReq.nextRequest(6);
+ System.out.println(next);
+ // Should use config md5 and generation from the request, not the response
+ // when there are errors
+ assertThat(next.getRequestConfigMd5(), is(clientReq.getRequestConfigMd5()));
+ assertThat(next.getRequestGeneration(), is(clientReq.getRequestGeneration()));
+ }
+
+ @Test
+ public void ok_response_is_added() {
+ Payload payload = createPayload("vale");
+ String md5 = ConfigUtils.getMd5(payload.getData());
+ long generation = 4l;
+ serverReq.addOkResponse(payload, generation, md5);
+ assertTrue(clientReq.validateResponse());
+ assertThat(clientReq.getNewPayload().withCompression(CompressionType.UNCOMPRESSED).getData().toString(), is(payload.getData().toString()));
+ assertThat(clientReq.getNewGeneration(), is(4l));
+ assertThat(clientReq.getNewConfigMd5(), is(md5));
+ assertTrue(clientReq.hasUpdatedConfig());
+ assertTrue(clientReq.hasUpdatedGeneration());
+ }
+
+ @Test
+ public void error_response_adds_common_elements() {
+ serverReq.addErrorResponse(ErrorCode.APPLICATION_NOT_LOADED, ErrorCode.getName(ErrorCode.APPLICATION_NOT_LOADED));
+ assertThat(serverReq.getRequest().returnValues().size(), is(1));
+ Slime data = new JsonDecoder().decode(new Slime(), Utf8.toBytes(serverReq.getRequest().returnValues().get(0).asString()));
+ Inspector response = data.get();
+ assertThat(response.field(SlimeResponseData.RESPONSE_DEF_NAME).asString(), is(defName));
+ assertThat(response.field(SlimeResponseData.RESPONSE_DEF_NAMESPACE).asString(), is(defNamespace));
+ assertThat(response.field(SlimeResponseData.RESPONSE_DEF_MD5).asString(), is(defMd5));
+ assertThat(response.field(SlimeResponseData.RESPONSE_CONFIGID).asString(), is(configId));
+ assertThat(response.field(SlimeResponseData.RESPONSE_CLIENT_HOSTNAME).asString(), is(hostname));
+ Trace t = Trace.fromSlime(response.field(SlimeResponseData.RESPONSE_TRACE));
+ assertThat(t.toString(), is(trace.toString()));
+ }
+
+ @Test
+ public void generation_only_is_updated() {
+ Payload payload = createPayload();
+ serverReq.addOkResponse(payload, 4l, ConfigUtils.getMd5(payload.getData()));
+ boolean value = clientReq.validateResponse();
+ assertTrue(clientReq.errorMessage(), value);
+ assertFalse(clientReq.hasUpdatedConfig());
+ assertTrue(clientReq.hasUpdatedGeneration());
+ }
+
+ protected static Payload createPayload() {
+ return createPayload("bar");
+ }
+
+ private static Payload createPayload(String value) {
+ Slime slime = new Slime();
+ slime.setObject().setString("myfield", value);
+ return Payload.from(new ConfigPayload(slime));
+ }
+
+ @Test
+ public void nothing_is_updated() {
+ Payload payload = createPayload();
+ serverReq.addOkResponse(payload, currentGeneration, configMd5);
+ assertTrue(clientReq.validateResponse());
+ assertFalse(clientReq.hasUpdatedConfig());
+ assertFalse(clientReq.hasUpdatedGeneration());
+ }
+
+ @Test
+ public void payload_is_empty() throws IOException {
+ Payload payload = Payload.from(ConfigPayload.empty());
+ clientReq = createReq(payload);
+ serverReq = createReq(clientReq.getRequest());
+ serverReq.addOkResponse(payload, currentGeneration, ConfigUtils.getMd5(payload.getData()));
+ boolean val = clientReq.validateResponse();
+ assertTrue(clientReq.errorMessage(), val);
+ assertFalse(clientReq.hasUpdatedConfig());
+ assertFalse(clientReq.hasUpdatedGeneration());
+ }
+
+ @Test
+ public void request_interface_is_implemented() {
+ JRTClientConfigRequest request = clientReq;
+ assertFalse(request.containsPayload());
+ assertFalse(request.isError());
+ assertThat(request.errorCode(), is(clientReq.getRequest().errorCode()));
+ assertThat(request.errorMessage(), is(clientReq.getRequest().errorMessage()));
+ assertNotNull(request.getRequest());
+ assertFalse(request.validateResponse());
+ //assertNull(request.getNewPayload().getData());
+ assertThat(request.getTimeout(), is(timeout));
+ assertFalse(request.hasUpdatedConfig());
+ assertFalse(request.hasUpdatedGeneration());
+ }
+
+ @Test
+ public void created_from_subscription() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ JRTConfigSubscription<SimpletypesConfig> sub = new JRTConfigSubscription<>(new ConfigKey<>(SimpletypesConfig.class, configId), subscriber, new ConfigSet(), new TimingValues());
+ JRTClientConfigRequest request = createReq(sub, Trace.createNew(9));
+ assertThat(request.getConfigKey().getName(), is(SimpletypesConfig.CONFIG_DEF_NAME));
+ JRTServerConfigRequest serverRequest = createReq(request.getRequest());
+ assertTrue(serverRequest.validateParameters());
+ }
+
+ @Test
+ public void created_from_existing_subscription() {
+ System.setProperty("VESPA_CONFIG_PROTOCOL_VERSION", getProtocolVersion());
+ MockConnection connection = new MockConnection(new MockConnection.AbstractResponseHandler() {
+ @Override
+ protected void createResponse() {
+ JRTServerConfigRequest serverRequest = createReq(request);
+ serverRequest.addOkResponse(createPayload(), currentGeneration, configMd5);
+ }
+ });
+
+ ConfigSourceSet src = new ConfigSourceSet();
+ ConfigSubscriber subscriber = new GenericConfigSubscriber(Collections.singletonMap(src, JRTConfigRequester.get(connection, new TimingValues())));
+ JRTConfigSubscription<SimpletypesConfig> sub = new JRTConfigSubscription<>(new ConfigKey<>(SimpletypesConfig.class, configId), subscriber, src, new TimingValues());
+ sub.subscribe(120_0000);
+ assertTrue(sub.nextConfig(120_0000));
+ sub.close();
+ JRTClientConfigRequest nextReq = createReq(sub, Trace.createNew());
+ SimpletypesConfig config = sub.getConfig();
+ assertThat(nextReq.getRequestConfigMd5(), is(config.getConfigMd5()));
+ assertThat(nextReq.getRequestGeneration(), is(currentGeneration));
+ System.setProperty("VESPA_CONFIG_PROTOCOL_VERSION", "");
+ }
+
+ protected abstract String getProtocolVersion();
+
+ @Test
+ public void created_from_raw() throws IOException {
+ RawConfig rawConfig = new RawConfig(new ConfigKey<>(defName, configId, defNamespace), defMd5);
+ long serverTimeout = 100000L;
+ JRTClientConfigRequest request = createFromRaw(rawConfig, serverTimeout, Trace.createNew(9));
+ assertThat(request.getConfigKey().getName(), is(defName));
+ JRTServerConfigRequest serverRequest = createReq(request.getRequest());
+ assertTrue(serverRequest.validateParameters());
+ assertThat(serverRequest.getTimeout(), is(serverTimeout));
+ assertThat(serverRequest.getDefContent().asList(), is(rawConfig.getDefContent()));
+ }
+
+
+ @Test
+ public void parameters_are_validated() throws IOException {
+ assertTrue(serverReq.validateParameters());
+ assertValidationFail(createReq("35#$#!$@#", defNamespace, defMd5, hostname, configId, configMd5, currentGeneration, timeout, trace));
+ assertValidationFail(createReq(defName, "abcd.o#$*(!&$", defMd5, hostname, configId, configMd5, currentGeneration, timeout, trace));
+ assertValidationFail(createReq(defName, defNamespace, "34", hostname, configId, "34", currentGeneration, timeout, trace));
+ assertValidationFail(createReq(defName, defNamespace, defMd5, hostname, configId, "34", currentGeneration, timeout, trace));
+ assertValidationFail(createReq(defName, defNamespace, defMd5, hostname, configId, configMd5, -34, timeout, trace));
+ assertValidationFail(createReq(defName, defNamespace, defMd5, hostname, configId, configMd5, currentGeneration, -23, trace));
+ assertValidationFail(createReq(defName, defNamespace, defMd5, "", configId, configMd5, currentGeneration, timeout, trace));
+ }
+
+ private void assertValidationFail(JRTClientConfigRequest req) {
+ assertFalse(createReq(req.getRequest()).validateParameters());
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestFactoryTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestFactoryTest.java
new file mode 100644
index 00000000000..ac3a7f16505
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestFactoryTest.java
@@ -0,0 +1,143 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.foo.FunctionTestConfig;
+import com.yahoo.config.subscription.ConfigSet;
+import com.yahoo.config.subscription.ConfigSubscriber;
+import com.yahoo.config.subscription.impl.JRTConfigSubscription;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.RawConfig;
+import com.yahoo.vespa.config.TimingValues;
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author musum
+ */
+public class JRTConfigRequestFactoryTest {
+ private static VespaVersion defaultVespaVersion = JRTConfigRequestFactory.getCompiledVespaVersion();
+
+ @Test
+ public void testGetProtocolVersion() {
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "", ""), is("3"));
+
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "", ""), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "1", ""), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "", "1"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "1", ""), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "", "1"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "1", "1"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "1", "1"), is("1"));
+
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "", ""), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "2", ""), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "", "2"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "2", ""), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "", "2"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "2", "2"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "2", "2"), is("2"));
+
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "2", ""), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "", "2"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "1", "2"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "1", ""), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "", "1"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("", "2", "1"), is("2"));
+
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "2", "2"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "1", "2"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("1", "2", "1"), is("1"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "1", "1"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "1", "2"), is("2"));
+ assertThat(JRTConfigRequestFactory.getProtocolVersion("2", "2", "1"), is("2"));
+ }
+
+ @Test
+ public void testCompressionType() {
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "", ""), is(CompressionType.LZ4));
+
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "", ""), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "UNCOMPRESSED", ""), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "", "UNCOMPRESSED"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "UNCOMPRESSED", ""), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "", "UNCOMPRESSED"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "UNCOMPRESSED", "UNCOMPRESSED"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "UNCOMPRESSED", "UNCOMPRESSED"), is(CompressionType.UNCOMPRESSED));
+
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "", ""), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "LZ4", ""), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "", "LZ4"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "LZ4", ""), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "", "LZ4"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "LZ4", "LZ4"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "LZ4", "LZ4"), is(CompressionType.LZ4));
+
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "LZ4", ""), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "", "LZ4"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "UNCOMPRESSED", "LZ4"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "UNCOMPRESSED", ""), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "", "UNCOMPRESSED"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("", "LZ4", "UNCOMPRESSED"), is(CompressionType.LZ4));
+
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "LZ4", "LZ4"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "UNCOMPRESSED", "LZ4"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("UNCOMPRESSED", "LZ4", "UNCOMPRESSED"), is(CompressionType.UNCOMPRESSED));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "UNCOMPRESSED", "UNCOMPRESSED"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "UNCOMPRESSED", "LZ4"), is(CompressionType.LZ4));
+ assertThat(JRTConfigRequestFactory.getCompressionType("LZ4", "LZ4", "UNCOMPRESSED"), is(CompressionType.LZ4));
+ }
+
+ @Test
+ public void testVespaVersion() {
+ assertThat(JRTConfigRequestFactory.getVespaVersion().get(), is(defaultVespaVersion));
+ }
+
+ @Test
+ public void testCreateFromSub() {
+ ConfigSubscriber subscriber = new ConfigSubscriber();
+ Class<FunctionTestConfig> clazz = FunctionTestConfig.class;
+ final String configId = "foo";
+ JRTConfigSubscription<FunctionTestConfig> sub = new JRTConfigSubscription<>(
+ new ConfigKey<>(clazz, configId), subscriber, new ConfigSet(), new TimingValues());
+
+ // Default vespa version
+ JRTClientConfigRequest request = JRTConfigRequestFactory.createFromSub(sub);
+ assertThat(request.getProtocolVersion(), is(3L));
+ assertThat(request.getVespaVersion().get(), is(defaultVespaVersion));
+
+ // Create with vespa version set
+ String version = "5.37.38";
+ System.setProperty(JRTConfigRequestFactory.VESPA_VERSION, version);
+ request = JRTConfigRequestFactory.createFromSub(sub);
+ assertThat(request.getProtocolVersion(), is(3L));
+ assertThat(request.getVespaVersion().get(), is(VespaVersion.fromString(version)));
+
+ System.clearProperty(JRTConfigRequestFactory.VESPA_VERSION);
+ }
+
+ @Test
+ public void testCreateFromRaw() {
+ Class<FunctionTestConfig> clazz = FunctionTestConfig.class;
+ final String configId = "foo";
+ RawConfig config = new RawConfig(new ConfigKey<>(clazz, configId), "595f44fec1e92a71d3e9e77456ba80d1");
+
+ // Default vespa version
+ JRTClientConfigRequest request = JRTConfigRequestFactory.createFromRaw(config, 1000);
+ assertThat(request.getProtocolVersion(), is(3L));
+ assertThat(request.getVespaVersion().get(), is(defaultVespaVersion));
+
+ // Create with vespa version set
+ String version = "5.37.38";
+ System.setProperty(JRTConfigRequestFactory.VESPA_VERSION, version);
+ request = JRTConfigRequestFactory.createFromRaw(config, 1000);
+ assertThat(request.getProtocolVersion(), is(3L));
+ assertThat(request.getVespaVersion().get(), is(VespaVersion.fromString(version)));
+
+ System.clearProperty(JRTConfigRequestFactory.VESPA_VERSION);
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestV3Test.java b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestV3Test.java
new file mode 100644
index 00000000000..bcb88b2ff5e
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/JRTConfigRequestV3Test.java
@@ -0,0 +1,78 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.config.subscription.impl.JRTConfigSubscription;
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.*;
+import com.yahoo.vespa.config.util.ConfigUtils;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.19
+ */
+public class JRTConfigRequestV3Test extends JRTConfigRequestBase {
+
+ @Override
+ protected JRTClientConfigRequest createReq(String defName, String defNamespace, String defMd5, String hostname, String configId, String configMd5, long currentGeneration, long timeout, Trace trace) throws IOException {
+ return JRTClientConfigRequestV3.createWithParams(ConfigKey.createFull(defName, configId, defNamespace, defMd5),
+ DefContent.fromList(Arrays.asList("namespace=my.name.space", "myfield string")),
+ hostname,
+ configMd5,
+ currentGeneration,
+ timeout,
+ trace,
+ CompressionType.LZ4,
+ vespaVersion);
+ }
+
+ @Override
+ protected JRTServerConfigRequest createReq(Request request) {
+ return JRTServerConfigRequestV3.createFromRequest(request);
+ }
+
+ @Override
+ protected JRTClientConfigRequest createReq(JRTConfigSubscription<SimpletypesConfig> sub, Trace aNew) {
+ return JRTClientConfigRequestV3.createFromSub(sub, aNew, CompressionType.LZ4, vespaVersion);
+ }
+
+ @Override
+ protected JRTClientConfigRequest createFromRaw(RawConfig rawConfig, long serverTimeout, Trace aNew) {
+ return JRTClientConfigRequestV3.createFromRaw(rawConfig, serverTimeout, aNew, CompressionType.LZ4, vespaVersion);
+ }
+
+ @Override
+ protected String getProtocolVersion() {
+ return "3";
+ }
+
+ @Test
+ public void request_is_parsed() {
+ request_is_parsed_base();
+ assertThat(serverReq.getVespaVersion().toString(), is(vespaVersion.toString()));
+ }
+
+ @Test
+ public void next_request_is_correct() {
+ JRTServerConfigRequest next = next_request_is_correct_base();
+ assertThat(next.getVespaVersion().toString(), is(vespaVersion.toString()));
+ }
+
+ @Test
+ public void emptypayload() {
+ ConfigPayload payload = ConfigPayload.empty();
+ SlimeConfigResponse response = SlimeConfigResponse.fromConfigPayload(payload, null, 0, ConfigUtils.getMd5(payload));
+ serverReq.addOkResponse(serverReq.payloadFromResponse(response), response.getGeneration(), response.getConfigMd5());
+ assertTrue(clientReq.validateResponse());
+ assertTrue(clientReq.hasUpdatedGeneration());
+ assertThat(clientReq.getNewPayload().withCompression(CompressionType.UNCOMPRESSED).getData().toString(), is("{}"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java
new file mode 100644
index 00000000000..c3cbfecf1f1
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/PayloadTest.java
@@ -0,0 +1,85 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.google.common.testing.EqualsTester;
+import com.yahoo.slime.Slime;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.LZ4PayloadCompressor;
+import org.junit.Test;
+
+import java.io.UnsupportedEncodingException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ * @since 5.21
+ */
+public class PayloadTest {
+
+ @Test
+ public void testUncompressedToCompressedWithoutCompressionInfo() {
+ String json = "{\"foo\":13}";
+ ConfigPayload configPayload = ConfigPayload.fromString(json);
+ Payload payload = Payload.from(configPayload);
+ assertThat(payload.getData().toString(), is(json));
+ payload = Payload.from(payload.getData(), CompressionInfo.create(CompressionType.UNCOMPRESSED, 0));
+ Payload compressed = payload.withCompression(CompressionType.LZ4);
+ Payload uncompressed = compressed.withCompression(CompressionType.UNCOMPRESSED);
+ assertThat(uncompressed.getData().toString(), is(json));
+ assertThat(compressed.toString(), is(json));
+ assertThat(uncompressed.toString(), is(json));
+ }
+
+ @Test
+ public void testEquals() {
+ final String foo1 = "foo 1";
+ final String foo2 = "foo 2";
+
+ Payload a = Payload.from(foo1);
+ Payload b = Payload.from(foo1);
+
+ Payload c = Payload.from(foo2);
+
+ Slime slime = new Slime();
+ slime.setString(foo1);
+ Payload d = Payload.from(new ConfigPayload(slime));
+
+ slime.setString(foo1);
+ Payload e = Payload.from(new ConfigPayload(slime));
+
+ slime.setString("foo 2");
+ Payload f = Payload.from(new ConfigPayload(slime));
+
+ Payload g = null;
+ Payload h = null;
+ Payload i = null;
+ Payload j = null;
+ try {
+ g = Payload.from(new Utf8Array(foo1.getBytes("UTF-8")), CompressionInfo.uncompressed());
+ h = Payload.from(new Utf8Array(foo1.getBytes("UTF-8")), CompressionInfo.uncompressed());
+
+ LZ4PayloadCompressor compressor = new LZ4PayloadCompressor();
+ CompressionInfo info = CompressionInfo.create(CompressionType.LZ4, foo2.length());
+ Utf8Array compressed = new Utf8Array(compressor.compress(foo2.getBytes()));
+
+ i = Payload.from(compressed, info);
+ j = Payload.from(compressed, info);
+ } catch (UnsupportedEncodingException e1) {
+ fail();
+ }
+
+ new EqualsTester()
+ .addEqualityGroup(a, b, g, h)
+ .addEqualityGroup(c)
+ .addEqualityGroup(d, e)
+ .addEqualityGroup(f)
+ .addEqualityGroup(i, j).
+ testEquals();
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/SlimeTraceSerializerTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/SlimeTraceSerializerTest.java
new file mode 100644
index 00000000000..4c69fa3e395
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/SlimeTraceSerializerTest.java
@@ -0,0 +1,123 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+import com.yahoo.text.Utf8;
+import com.yahoo.yolean.trace.TraceNode;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.5
+ */
+public class SlimeTraceSerializerTest {
+ @Test
+ public void test_serializer() throws IOException {
+ TraceNode root = new TraceNode(null, 1);
+ root.add(new TraceNode("foo", 4));
+ root.add(new TraceNode("bar", 5).add(new TraceNode("baz", 2).add(new TraceNode("quux", 10))));
+ assertThat(toJson(root), is("{\"timestamp\":1,\"children\":[{\"timestamp\":5,\"payload\":\"bar\",\"children\":[{\"timestamp\":2,\"payload\":\"baz\",\"children\":[{\"timestamp\":10,\"payload\":\"quux\"}]}]},{\"timestamp\":4,\"payload\":\"foo\"}]}"));
+ assertSerialize(root);
+ }
+
+ private String toJson(TraceNode root) throws IOException {
+ Slime slime = new Slime();
+ SlimeTraceSerializer serializer = new SlimeTraceSerializer(slime.setObject());
+ root.accept(serializer);
+ JsonFormat format = new JsonFormat(true);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ format.encode(baos, slime);
+ return Utf8.toString(baos.toByteArray());
+ }
+
+ private void assertSerialize(TraceNode root) {
+ Slime slime = new Slime();
+ SlimeTraceSerializer serializer = new SlimeTraceSerializer(slime.setObject());
+ root.accept(serializer);
+ SlimeTraceDeserializer deserializer = new SlimeTraceDeserializer(slime.get());
+ TraceNode deser = deserializer.deserialize();
+ assertTraceEqual(deser, root);
+ }
+
+ private void assertTraceEqual(TraceNode deser, TraceNode root) {
+ assertThat(deser.timestamp(), is(root.timestamp()));
+ assertThat(deser.payload(), is(root.payload()));
+ Iterator<TraceNode> actualIt = deser.children().iterator();
+ Iterator<TraceNode> expectedIt = root.children().iterator();
+ Map<Long, TraceNode> expectedMapping = new HashMap<>();
+ Map<Long, TraceNode> actualMapping = new HashMap<>();
+ while (expectedIt.hasNext()) {
+ assertTrue(actualIt.hasNext());
+ TraceNode actualNode = actualIt.next();
+ TraceNode expectedNode = expectedIt.next();
+ expectedMapping.put(expectedNode.timestamp(), expectedNode);
+ actualMapping.put(actualNode.timestamp(), actualNode);
+ }
+ assertFalse(expectedIt.hasNext());
+ assertFalse(actualIt.hasNext());
+ for (long timestamp : expectedMapping.keySet()) {
+ assertTraceEqual(actualMapping.get(timestamp), expectedMapping.get(timestamp));
+ }
+ }
+
+ @Test
+ public void test_long() throws IOException {
+ TraceNode root = new TraceNode(14l, 5);
+ assertThat(toJson(root), is("{\"timestamp\":5,\"payload\":14}"));
+ assertSerialize(root);
+ }
+
+ @Test
+ public void test_double() throws IOException {
+ TraceNode root = new TraceNode(3.5, 5);
+ assertThat(toJson(root), is("{\"timestamp\":5,\"payload\":3.5}"));
+ assertSerialize(root);
+ }
+
+ @Test
+ public void test_bool() throws IOException {
+ TraceNode root = new TraceNode(true, 5);
+ assertThat(toJson(root), is("{\"timestamp\":5,\"payload\":true}"));
+ assertSerialize(root);
+ }
+
+ @Test
+ public void test_string() throws IOException {
+ TraceNode root = new TraceNode("bar", 5);
+ assertThat(toJson(root), is("{\"timestamp\":5,\"payload\":\"bar\"}"));
+ assertSerialize(root);
+ }
+
+ @Test
+ public void test_unknown() throws IOException {
+ TraceNode root = new TraceNode(new ArrayList<String>(), 5);
+ assertThat(toJson(root), is("{\"timestamp\":5}"));
+ }
+
+ @Test
+ public void test_null() throws IOException {
+ TraceNode root = new TraceNode(null, 5);
+ assertThat(toJson(root), is("{\"timestamp\":5}"));
+ assertSerialize(root);
+ }
+
+ @Test
+ public void test_bytearray() throws IOException {
+ TraceNode root = new TraceNode(new byte[] { 3, 5 }, 5);
+ assertThat(toJson(root), is("{\"timestamp\":5,\"payload\":\"0x0305\"}"));
+ assertSerialize(root);
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/protocol/TraceTest.java b/config/src/test/java/com/yahoo/vespa/config/protocol/TraceTest.java
new file mode 100644
index 00000000000..3e9d0650ae5
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/protocol/TraceTest.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.protocol;
+
+import com.yahoo.slime.Slime;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.3
+ */
+public class TraceTest {
+
+ @Test
+ public void trace_level_limits_tracing() {
+ Trace trace = Trace.createNew(3);
+ assertTrue(trace.shouldTrace(0));
+ assertTrue(trace.shouldTrace(1));
+ assertTrue(trace.shouldTrace(2));
+ assertTrue(trace.shouldTrace(3));
+ assertFalse(trace.shouldTrace(4));
+ assertFalse(trace.shouldTrace(5));
+ trace.trace(1, "foo");
+ trace.trace(1, "foo2");
+ trace.trace(2, "bar");
+ trace.trace(3, "baz");
+ trace.trace(3, "baz2");
+ trace.trace(4, "quux");
+ trace.trace(5, "quux2");
+ String str = trace.toString();
+ assertTrue(str.contains("foo"));
+ assertTrue(str.contains("foo2"));
+ assertTrue(str.contains("bar"));
+ assertTrue(str.contains("baz"));
+ assertTrue(str.contains("baz2"));
+ assertFalse(str.contains("quux"));
+ assertFalse(str.contains("quux2"));
+ }
+
+ @Test
+ public void trace_serialization() {
+ Trace trace = Trace.createNew(1);
+ trace.trace(0, "foobar");
+ trace.trace(1, "barbaz");
+ Slime slime = new Slime();
+ trace.serialize(slime.setObject());
+ Trace trace2 = Trace.fromSlime(slime.get());
+ trace2.trace(1, "quux");
+ String trace1Str = trace.toString();
+ String trace2Str = trace2.toString();
+ assertTrue(trace1Str.contains("foobar"));
+ assertTrue(trace1Str.contains("barbaz"));
+ assertFalse(trace1Str.contains("quux"));
+
+ assertTrue(trace2Str.contains("foobar"));
+ assertTrue(trace2Str.contains("barbaz"));
+ assertTrue(trace2Str.contains("quux"));
+ }
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java b/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java
new file mode 100644
index 00000000000..d07c007c13f
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/util/ConfigUtilsTest.java
@@ -0,0 +1,288 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.util;
+
+import com.yahoo.collections.Tuple2;
+import com.yahoo.foo.SimpletypesConfig;
+import com.yahoo.io.IOUtils;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests ConfigUtils.
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigUtilsTest {
+
+ @Test
+ public void testGetDefMd5() {
+ String expectedMd5 = "a29038b17c727dabc572a967a508dc1f";
+ List<String> lines = new ArrayList<>();
+
+ // Create normalized lines
+ lines.add("a");
+ lines.add("foo=1 # a comment");
+ lines.add("int a default=1 range = [,]");
+ lines.add(""); //empty line should not affect md5sum
+ lines.add("double b default=1.0 range = [,]");
+ lines.add("collectiontype enum { SINGLE, ARRAY, WEIGHTEDSET } default=SINGLE");
+
+ assertThat(ConfigUtils.getDefMd5(lines), is(expectedMd5));
+
+ lines.clear();
+
+ // Test various normalizing features implemented by getMd5
+
+ // Check that lines are trimmed
+ lines.add("a ");
+ // Check that trailing comments are trimmed
+ lines.add("foo=1");
+ // Check that upper and lower bounds for int and double ranges are set correctly
+ lines.add("int a default=1 range = [-2147483648,2147483647]");
+ lines.add("double b default=1.0 range = [-100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000," +
+ "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]");
+ // check that space before commas are treated correctly
+ lines.add("collectiontype enum { SINGLE , ARRAY , WEIGHTEDSET } default=SINGLE");
+ assertThat(ConfigUtils.getDefMd5(lines), is(expectedMd5));
+ }
+
+ @Test
+ public void testGetMd5WithComment() {
+ String expectedMd5 = "4395db1dfbd977c4d74190d2d23396e2";
+ List<String> lines = new ArrayList<>();
+
+ // Create normalized lines
+ lines.add("foo=\"1#hello\"");
+ lines.add(""); //empty line should not affect md5sum
+
+ assertThat(ConfigUtils.getMd5(lines), is(expectedMd5));
+
+ lines.clear();
+
+ // Check that comment character in string leads to a different md5 than the original
+ lines.add("foo=\"1#hello and some more\"");
+ String md5 = ConfigUtils.getMd5(lines);
+ assertThat(md5, is(not(expectedMd5)));
+
+ // Check that added characters aft comment character in string leads to a different md5 than above
+ lines.add("foo=\"1#hello and some more and even more\"");
+ assertThat(ConfigUtils.getMd5(lines), is(not(md5)));
+ }
+
+ @Test
+ public void testGetMd5OfPayload() {
+ String expectedMd5 = "c9246ed8c8ab55b1c463c501c84075e6";
+ String expectedChangedMd5 = "f6f81062ef5f024f1912798490ba7dfc";
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ System.out.println(payload);
+ assertThat(ConfigUtils.getMd5(payload), is(expectedMd5));
+ payload.getSlime().get().setString("fabio", "bar");
+ System.out.println(payload);
+ assertThat(ConfigUtils.getMd5(payload), is(expectedChangedMd5));
+ }
+
+ @Test
+ public void testGetMd5OfString() {
+ String expectedMd5 = "c9246ed8c8ab55b1c463c501c84075e6";
+ String expectedChangedMd5 = "f6f81062ef5f024f1912798490ba7dfc";
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ System.out.println(payload);
+ assertThat(ConfigUtils.getMd5(payload.toString(true)), is(expectedMd5));
+ payload.getSlime().get().setString("fabio", "bar");
+ System.out.println(payload);
+ assertThat(ConfigUtils.getMd5(payload.toString(true)), is(expectedChangedMd5));
+ }
+
+ @Test
+ public void testStripSpaces() {
+ assertThat(ConfigUtils.stripSpaces("a b"), is("a b"));
+ assertThat(ConfigUtils.stripSpaces("\"a b\""), is("\"a b\""));
+ assertThat(ConfigUtils.stripSpaces("a b \"a b\""), is("a b \"a b\""));
+ assertThat(ConfigUtils.stripSpaces("a b"), is("a b"));
+ }
+
+ @Test
+ public void testGetVersion() {
+ StringReader reader = new StringReader("version=1\nint a default=0");
+ assertThat(ConfigUtils.getDefVersion(reader), is("1"));
+
+ // no version
+ reader = new StringReader("int a default=0");
+ assertThat(ConfigUtils.getDefVersion(reader), is(""));
+
+ // namespace and version
+ reader = new StringReader("version=1\nnamespace=foo\nint a default=0");
+ assertThat(ConfigUtils.getDefVersion(reader), is("1"));
+ reader = new StringReader("namespace=foo\nversion=1\nint a default=0");
+ assertThat(ConfigUtils.getDefVersion(reader), is("1"));
+ }
+
+ @Test
+ public void testGetNamespace() {
+ StringReader reader = new StringReader("version=1\nnamespace=a\nint a default=0");
+ assertThat(ConfigUtils.getDefNamespace(reader), is("a"));
+ // namespace first
+ reader = new StringReader("namespace=a\nversion=1\nint a default=0");
+ assertThat(ConfigUtils.getDefNamespace(reader), is("a"));
+
+ // No namespace
+ reader = new StringReader("version=1\nint a default=0");
+ assertThat(ConfigUtils.getDefNamespace(reader), is(""));
+
+ // comment lines
+ reader = new StringReader("#comment\nversion=1\n#comment2\nint a default=0");
+ assertThat(ConfigUtils.getDefNamespace(reader), is(""));
+
+ try {
+ ConfigUtils.getDefNamespace(null);
+ fail();
+ } catch (IllegalArgumentException e) {
+ //
+ }
+ }
+
+ @Test
+ public void testGetNameCommaVersion() {
+ String nameCommaversion = "foo,1";
+ Tuple2<String, String> tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion);
+ assertThat(tuple.first, is("foo"));
+ assertThat(tuple.second, is("1"));
+
+ // no version
+ nameCommaversion = "foo";
+ tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion);
+ assertThat(tuple.first, is("foo"));
+ assertThat(tuple.second, is(""));
+
+ // no name
+ nameCommaversion = ",1";
+ tuple = ConfigUtils.getNameAndVersionFromString(nameCommaversion);
+ assertThat(tuple.first, is(""));
+ assertThat(tuple.second, is("1"));
+ }
+
+ @Test
+ public void testNamespaceDotNames() {
+ String namespaceDotName = "foo.bar";
+ Tuple2<String, String> tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName);
+ assertThat(tuple.first, is("bar"));
+ assertThat(tuple.second, is("foo"));
+
+ namespaceDotName = "foo.baz.bar";
+ tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName);
+ assertThat(tuple.first, is("bar"));
+ assertThat(tuple.second, is("foo.baz"));
+
+ // no namespace
+ namespaceDotName = "bar";
+ tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName);
+ assertThat(tuple.first, is("bar"));
+ assertThat(tuple.second, is(""));
+
+ // no name
+ namespaceDotName = "foo.";
+ tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName);
+ assertThat(tuple.first, is(""));
+ assertThat(tuple.second, is("foo"));
+
+ // no namespace
+ namespaceDotName = ".bar";
+ tuple = ConfigUtils.getNameAndNamespaceFromString(namespaceDotName);
+ assertThat(tuple.first, is("bar"));
+ assertThat(tuple.second, is(""));
+ }
+
+ @Test
+ public void testGetConfigDefinitionKey() {
+ String input = "foo";
+ ConfigDefinitionKey def = ConfigUtils.getConfigDefinitionKeyFromString(input);
+ assertThat(def.getName(), is("foo"));
+ assertThat(def.getNamespace(), is(""));
+
+ input = "foo.bar";
+ def = ConfigUtils.getConfigDefinitionKeyFromString(input);
+ assertThat(def.getName(), is("bar"));
+ assertThat(def.getNamespace(), is("foo"));
+
+ input = "foo.bar.1";
+ def = ConfigUtils.getConfigDefinitionKeyFromString(input);
+ assertThat(def.getName(), is("bar"));
+ assertThat(def.getNamespace(), is("foo"));
+
+ input = "foo.bar.qux.2";
+ def = ConfigUtils.getConfigDefinitionKeyFromString(input);
+ assertThat(def.getName(), is("qux"));
+ assertThat(def.getNamespace(), is("foo.bar"));
+
+ input = "foo.2";
+ def = ConfigUtils.getConfigDefinitionKeyFromString(input);
+ assertThat(def.getName(), is("foo"));
+ assertThat(def.getNamespace(), is(""));
+ }
+
+ @Test
+ public void testCreateConfigDefinitionKeyFromZKString() {
+ String input = "foo,1";
+ ConfigDefinitionKey def = ConfigUtils.createConfigDefinitionKeyFromZKString(input);
+ assertThat(def.getName(), is("foo"));
+ assertThat(def.getNamespace(), is(""));
+
+ input = "bar.foo,1";
+ def = ConfigUtils.createConfigDefinitionKeyFromZKString(input);
+ assertThat(def.getName(), is("foo"));
+ assertThat(def.getNamespace(), is("bar"));
+ }
+
+ @Test
+ public void testCreateConfigDefinitionKeyFromDefFile() {
+ ConfigDefinitionKey def = null;
+ try {
+ def = ConfigUtils.createConfigDefinitionKeyFromDefFile(new File("src/test/resources/configs/def-files/app.def"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail();
+ }
+ assertThat(def.getName(), is("app"));
+ assertThat(def.getNamespace(), is("foo"));
+
+ try {
+ def = ConfigUtils.createConfigDefinitionKeyFromDefFile(new File("src/test/resources/configs/def-files/testnamespace.def"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail();
+ }
+ assertThat(def.getName(), is("testnamespace"));
+ assertThat(def.getNamespace(), is("foo"));
+
+ try {
+ byte[] content = IOUtils.readFileBytes(new File("src/test/resources/configs/def-files/app.def"));
+ def = ConfigUtils.createConfigDefinitionKeyFromDefContent("app", content);
+ } catch (IOException e) {
+ fail();
+ }
+ assertThat(def.getName(), is("app"));
+ assertThat(def.getNamespace(), is("foo"));
+
+ try {
+ byte[] content = IOUtils.readFileBytes(new File("src/test/resources/configs/def-files-nogen/foo.bar.app.def"));
+ def = ConfigUtils.createConfigDefinitionKeyFromDefContent("app", content);
+ } catch (IOException e) {
+ fail();
+ }
+ assertThat(def.getName(), is("app"));
+ assertThat(def.getNamespace(), is("mynamespace"));
+ }
+
+}
diff --git a/config/src/test/java/com/yahoo/vespa/config/xml/whitespace-test.xml b/config/src/test/java/com/yahoo/vespa/config/xml/whitespace-test.xml
new file mode 100644
index 00000000000..6f63e405df8
--- /dev/null
+++ b/config/src/test/java/com/yahoo/vespa/config/xml/whitespace-test.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<config name="string">
+ <stringVal> This is a string
+ that contains different kinds of whitespace </stringVal>
+</config>
diff --git a/config/src/test/resources/configdefinitions/bar.def b/config/src/test/resources/configdefinitions/bar.def
new file mode 100644
index 00000000000..9293df5afd5
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/bar.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+barValue string default="defaultBar"
diff --git a/config/src/test/resources/configdefinitions/baz.def b/config/src/test/resources/configdefinitions/baz.def
new file mode 100644
index 00000000000..a505df39830
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/baz.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+bazValue string default="defaultBaz"
diff --git a/config/src/test/resources/configdefinitions/bootstrap.def b/config/src/test/resources/configdefinitions/bootstrap.def
new file mode 100644
index 00000000000..8cd61352a09
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/bootstrap.def
@@ -0,0 +1,6 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+component[].name string
+component[].configid string
+
diff --git a/config/src/test/resources/configdefinitions/foo.def b/config/src/test/resources/configdefinitions/foo.def
new file mode 100644
index 00000000000..ace1ed3676d
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/foo.def
@@ -0,0 +1,7 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+fooValue string
+fooArray[] int
+fooStruct[].innerStruct[].bar int
+fooMap{} int
diff --git a/config/src/test/resources/configdefinitions/foobar.def b/config/src/test/resources/configdefinitions/foobar.def
new file mode 100644
index 00000000000..427abfa8d5d
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/foobar.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+fooBarValue string default="defaultFooBar"
diff --git a/config/src/test/resources/configdefinitions/foodefault.def b/config/src/test/resources/configdefinitions/foodefault.def
new file mode 100644
index 00000000000..06d84a092cf
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/foodefault.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+fooValue string default = "per"
diff --git a/config/src/test/resources/configdefinitions/function-test.def b/config/src/test/resources/configdefinitions/function-test.def
new file mode 100644
index 00000000000..40047418eb0
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/function-test.def
@@ -0,0 +1,74 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#
+# This def file should test most aspects of def files that makes a difference
+# for the autogenerated config classes. The goal is to trigger all blocks of
+# code in the code generators. This includes:
+#
+# - Use all legal special characters in the def file name, to ensure that those
+# that needs to be replaced in type names are actually replaced.
+# - Use the same enum type twice to verify that we dont declare or define it
+# twice.
+# - Use the same struct type twice for the same reason.
+# - Include arrays of primitives and structs.
+# - Include enum primitives and array of enums. Arrays of enums must be handled
+# specially by the C++ code.
+# - Include enums both with and without default values.
+# - Include primitive string, numbers & doubles both with and without default
+# values.
+# - Have an array within a struct, to verify that we correctly recurse.
+# - Reuse type name further within to ensure that this works.
+
+namespace=config
+
+# Some random bool without a default value. These comments exist to check
+ # that comment parsing works.
+bool_val bool
+ ## A bool with a default value set.
+bool_with_def bool default=false
+int_val int
+int_with_def int default=-545
+long_val long
+long_with_def long default=-50000000000
+double_val double
+double_with_def double default=-6.43
+# Another comment
+string_val string
+stringwithdef string default="foobar"
+enum_val enum { FOO, BAR, FOOBAR }
+enumwithdef enum { FOO2, BAR2, FOOBAR2 } default=BAR2
+onechoice enum { ONLYFOO } default=ONLYFOO
+refval reference
+refwithdef reference default=":parent:"
+fileVal file
+
+boolarr[] bool
+intarr[] int
+longarr[] long
+doublearr[] double
+stringarr[] string
+enumarr[] enum { ARRAY, VALUES }
+refarr[] reference
+fileArr[] file
+
+# A basic struct
+basicStruct.foo string default="basic"
+basicStruct.bar int
+basicStruct.intArr[] int
+
+# A struct of struct
+rootStruct.inner0.name string default="inner0"
+rootStruct.inner0.index int
+rootStruct.inner1.name string default="inner1"
+rootStruct.inner1.index int
+rootStruct.inner2.array[] int
+rootStruct.innerArr[].boolVal bool default=false
+rootStruct.innerArr[].stringVal string
+
+myarray[].intval int default=14
+myarray[].stringval[] string
+myarray[].enumval enum { INNER, ENUM, TYPE } default=TYPE
+myarray[].refval reference # Value in array without default
+myarray[].fileVal file
+myarray[].anotherarray[].foo int default=-4
+myarray[].myStruct.a int
+myarray[].myStruct.b int default=2
diff --git a/config/src/test/resources/configdefinitions/motd.def b/config/src/test/resources/configdefinitions/motd.def
new file mode 100644
index 00000000000..554ffa8fb28
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/motd.def
@@ -0,0 +1,84 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#
+# This def file should test most aspects of def files that makes a difference
+# for the autogenerated config classes. The goal is to trigger all blocks of
+# code in the code generators. This includes:
+#
+# - Use all legal special characters in the def file name, to ensure that those
+# that needs to be replaced in type names are actually replaced.
+# - Use the same enum type twice to verify that we dont declare or define it
+# twice.
+# - Use the same struct type twice for the same reason.
+# - Include arrays of primitives and structs.
+# - Include enum primitives and array of enums. Arrays of enums must be handled
+# specially by the C++ code.
+# - Include enums both with and without default values.
+# - Include primitive string, numbers & doubles both with and without default
+# values.
+# - Have an array within a struct, to verify that we correctly recurse.
+# - Reuse type name further within to ensure that this works.
+
+namespace=config
+
+# Some random bool without a default value. These comments exist to check
+ # that comment parsing works.e
+boolVal bool
+ ## A bool with a default value set.
+bool_with_def bool default=false
+intVal int
+intWithDef int default=-545
+longVal long
+longWithDef long default=1234567890123
+doubleVal double
+double_with_def double default=-6.43
+# Another comment
+stringVal string
+stringwithdef string default="foobar"
+stringnulldef string default="null"
+enumVal enum { FOO, BAR, FOOBAR }
+enumwithdef enum { FOO2, BAR2, FOOBAR2 } default=BAR2
+refVal reference
+refwithdef reference default=":parent:"
+fileVal file
+
+boolarr[] bool
+intarr[] int
+longarr[] long
+doublearr[] double
+stringarr[] string
+enumarr[] enum { ARRAY, VALUES }
+refarr[] reference
+filearr[] file
+
+boolmap{} bool
+intmap{} int
+longmap{} long
+doublemap{} double
+stringmap{} string
+enummap{} enum { LOL1, LOL2 }
+refmap{} reference
+filemap{} file
+
+structmap{}.foo int
+mapmap{}.map{}.bar int
+
+# A basic struct
+basic_struct.foo string default="foo"
+basic_struct.bar int default=0
+
+# A struct of struct
+struct_of_struct.inner0.name string default="inner0"
+struct_of_struct.inner0.index int default=0
+struct_of_struct.inner1.name string default="inner1"
+struct_of_struct.inner1.index int default=1
+
+myArray[].intVal int default=14
+myArray[].stringVal[] string
+myArray[].enumVal enum { INNER, ENUM, TYPE } default=TYPE
+myArray[].refVal reference # Value in array without default
+myArray[].anotherArray[].foo int default=-4
+
+value string default="value"
+buffer int default=-1
+rhs string default="rhs"
+lines string default="lines"
diff --git a/config/src/test/resources/configdefinitions/my.def b/config/src/test/resources/configdefinitions/my.def
new file mode 100644
index 00000000000..8c6728fc63c
--- /dev/null
+++ b/config/src/test/resources/configdefinitions/my.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config
+
+myField string
diff --git a/config/src/test/resources/configs/bar/app.cfg b/config/src/test/resources/configs/bar/app.cfg
new file mode 100644
index 00000000000..8e4096805ea
--- /dev/null
+++ b/config/src/test/resources/configs/bar/app.cfg
@@ -0,0 +1,7 @@
+message "msg2"
+times 2
+a[4]
+a[0].name "a0"
+a[1].name "a1"
+a[2].name "a2"
+a[3].name "a3"
diff --git a/config/src/test/resources/configs/bar/string.cfg b/config/src/test/resources/configs/bar/string.cfg
new file mode 100644
index 00000000000..5cb1a496e0e
--- /dev/null
+++ b/config/src/test/resources/configs/bar/string.cfg
@@ -0,0 +1 @@
+stringVal "My mess"
diff --git a/config/src/test/resources/configs/baz/app.1.cfg.new b/config/src/test/resources/configs/baz/app.1.cfg.new
new file mode 100644
index 00000000000..b734dd17297
--- /dev/null
+++ b/config/src/test/resources/configs/baz/app.1.cfg.new
@@ -0,0 +1,85 @@
+times 3
+a[0]=1
+a[2]=1
+a[3]=1
+a[4]=1
+a[5]=1
+a[6]=1
+a[7]=1
+a[8]=1
+a[9]=1
+a[10]=1
+a[11]=1
+a[12]=1
+a[13]=1
+a[14]=1
+a[15]=1
+a[16]=1
+a[17]=1
+a[18]=1
+a[19]=1
+a[20]=1
+a[21]=1
+a[22]=1
+a[23]=1
+a[24]=1
+a[25]=1
+a[26]=1
+a[27]=1
+a[28]=1
+a[29]=1
+a[30]=1
+a[31]=1
+a[32]=1
+a[33]=1
+a[34]=1
+a[35]=1
+a[36]=1
+a[37]=1
+a[38]=1
+a[39]=1
+a[40]=1
+a[41]=1
+a[42]=1
+a[43]=1
+a[44]=1
+a[45]=1
+a[46]=1
+a[47]=1
+a[48]=1
+a[49]=1
+a[50]=1
+a[51]=1
+a[52]=1
+a[53]=1
+a[54]=1
+a[55]=1
+a[56]=1
+a[57]=1
+a[58]=1
+a[59]=1
+a[60]=1
+a[61]=1
+a[62]=1
+a[63]=1
+a[64]=1
+a[65]=1
+a[66]=1
+a[67]=1
+a[68]=1
+a[69]=1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 1
+version 2
diff --git a/config/src/test/resources/configs/baz/app.cfg b/config/src/test/resources/configs/baz/app.cfg
new file mode 100644
index 00000000000..b04f16fe7c9
--- /dev/null
+++ b/config/src/test/resources/configs/baz/app.cfg
@@ -0,0 +1,5 @@
+times 3
+a[3]
+a[0].name "a0"
+a[1].name "a1"
+a[2].name "a2"
diff --git a/config/src/test/resources/configs/datastructures/config1.txt b/config/src/test/resources/configs/datastructures/config1.txt
new file mode 100644
index 00000000000..f6121cfa486
--- /dev/null
+++ b/config/src/test/resources/configs/datastructures/config1.txt
@@ -0,0 +1,17 @@
+date[3]
+date[0] 14-Apr-08
+date[1] 15-Apr-08
+date[2] 16-Apr-08
+stock[2]
+stock[0].ticker YHOO
+stock[0].type COMMON
+stock[0].volume[3]
+stock[0].volume[0] 1333000
+stock[0].volume[1] 996000
+stock[0].volume[2] 2400000
+stock[1].ticker EWJ
+stock[1].type ETF
+stock[1].volume[3]
+stock[1].volume[0] 4600000
+stock[1].volume[1] 3002000
+stock[1].volume[2] 10987000
diff --git a/config/src/test/resources/configs/datastructures/config1_1.txt b/config/src/test/resources/configs/datastructures/config1_1.txt
new file mode 100644
index 00000000000..33a6d6c22aa
--- /dev/null
+++ b/config/src/test/resources/configs/datastructures/config1_1.txt
@@ -0,0 +1,14 @@
+date[2]
+date[0] 16-Apr-08
+date[1] 17-Apr-08
+stock[2]
+stock[0].ticker YHOO
+stock[0].type COMMON
+stock[0].volume[2]
+stock[0].volume[0] 1333000
+stock[0].volume[1] 800800
+stock[1].ticker JNJ
+stock[1].type COMMON
+stock[1].volume[2]
+stock[1].volume[0] 10987000
+stock[1].volume[1] 1300000
diff --git a/config/src/test/resources/configs/datastructures/config2.txt b/config/src/test/resources/configs/datastructures/config2.txt
new file mode 100644
index 00000000000..6cc31add025
--- /dev/null
+++ b/config/src/test/resources/configs/datastructures/config2.txt
@@ -0,0 +1,19 @@
+date[3]
+date[0] 14-Apr-08
+date[1] 15-Apr-08
+date[2] 16-Apr-08
+stock[2]
+stock[0].ticker YHOO
+stock[0].type COMMON
+stock[0].volume[3]
+stock[0].volume[0] 1333000
+stock[0].volume[1] 996000
+stock[0].volume[2] 2400000
+stock[1].ticker EWJ
+stock[1].type ETF
+stock[1].volume[3]
+stock[1].volume[0] 4600000
+stock[1].volume[1] 3002000
+stock[1].volume[2] 10987000
+basicstruct.foo "bar"
+basicstruct.bar 42
diff --git a/config/src/test/resources/configs/def-files-nogen/foo.bar.app.def b/config/src/test/resources/configs/def-files-nogen/foo.bar.app.def
new file mode 100644
index 00000000000..d2a1df70e7a
--- /dev/null
+++ b/config/src/test/resources/configs/def-files-nogen/foo.bar.app.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=mynamespace
+
+message string default="Hello!"
+
+times int default=1
+
+a[].name string
diff --git a/config/src/test/resources/configs/def-files/app.def b/config/src/test/resources/configs/def-files/app.def
new file mode 100644
index 00000000000..30919c866e7
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/app.def
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+version=1
+
+message string default="Hello!"
+
+times int default=1
+
+a[].name string
diff --git a/config/src/test/resources/configs/def-files/arraytypes.def b/config/src/test/resources/configs/def-files/arraytypes.def
new file mode 100644
index 00000000000..b6e206ff925
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/arraytypes.def
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only simple array types that can be used for testing
+# individual types in detail.
+namespace=foo
+version=1
+
+boolarr[] bool
+doublearr[] double
+enumarr[] enum { VAL1, VAL2 }
+intarr[] int
+longarr[] long
+stringarr[] string
diff --git a/config/src/test/resources/configs/def-files/chains-test.def b/config/src/test/resources/configs/def-files/chains-test.def
new file mode 100644
index 00000000000..26a23ff17da
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/chains-test.def
@@ -0,0 +1,43 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Chains configuration
+namespace=foo
+version=12
+
+component[].id string
+
+# Configured functionality provided by this - comes in addition to those set in the code
+component[].dependencies.provides[] string
+
+# Configured "before" dependencies provided by this - comes in addition to those set in the code
+component[].dependencies.before[] string
+
+# Configured "after" dependencies provided by this - comes in addition to those set in the code
+component[].dependencies.after[] string
+
+# The id of this chain. The id has the form name(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+# The default chain must be called "default".
+chain[].id string
+
+#The type of this chain
+chain[].type enum {DOCPROC, SEARCH} default=SEARCH
+
+# The id of a component to include in this chain.
+# The id has the form fullclassname(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+chain[].component[] string
+
+# The optional list of chain ids this inherits.
+# The ids has the form name(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+# If the version is not specified the newest version is used.
+chain[].inherit[] string
+
+# The optional list of component ids to exclude from this chain even if they exists in inherited chains
+# If versions are specified in these ids, they are ignored.
+chain[].exclude[] string
+
+# The phases for a chain
+chain[].phase[].id string
+chain[].phase[].before[] string
+chain[].phase[].after[] string
diff --git a/config/src/test/resources/configs/def-files/datastructures.def b/config/src/test/resources/configs/def-files/datastructures.def
new file mode 100644
index 00000000000..803931c91bc
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/datastructures.def
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+version=4
+
+date[] string
+
+stock[].ticker string
+stock[].type enum { COMMON, ETF, ETC } default=COMMON
+stock[].volume[] int
+
+basicstruct.foo string default="foo"
+basicstruct.bar int default=0
diff --git a/config/src/test/resources/configs/def-files/defaulttest.def b/config/src/test/resources/configs/def-files/defaulttest.def
new file mode 100644
index 00000000000..3934a0e4d12
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/defaulttest.def
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+version=3
+
+nondefaultstring string
+defaultstring string default="thedefault"
+
+nondefaultreference reference
+defaultreference reference default="thedefault"
diff --git a/config/src/test/resources/configs/def-files/function-test.def b/config/src/test/resources/configs/def-files/function-test.def
new file mode 100644
index 00000000000..4529630c6b1
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/function-test.def
@@ -0,0 +1,89 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#
+# This def file should test most aspects of def files that makes a difference
+# for the autogenerated config classes. The goal is to trigger all blocks of
+# code in the code generators. This includes:
+#
+# - Use all legal special characters in the def file name, to ensure that those
+# that needs to be replaced in type names are actually replaced.
+# - Use the same enum type twice to verify that we dont declare or define it
+# twice.
+# - Use the same struct type twice for the same reason.
+# - Include arrays of primitives and structs.
+# - Include enum primitives and array of enums. Arrays of enums must be handled
+# specially by the C++ code.
+# - Include enums both with and without default values.
+# - Include primitive string, numbers & doubles both with and without default
+# values.
+# - Have an array within a struct, to verify that we correctly recurse.
+# - Reuse type name further within to ensure that this works.
+
+version=8
+
+namespace=foo
+
+# Some random bool without a default value. These comments exist to check
+ # that comment parsing works.
+bool_val bool
+ ## A bool with a default value set.
+bool_with_def bool default=false
+int_val int
+int_with_def int default=-545
+long_val long
+long_with_def long default=-50000000000
+double_val double
+double_with_def double default=-6.43
+# Another comment
+string_val string
+stringwithdef string default="foobar"
+enum_val enum { FOO, BAR, FOOBAR }
+enumwithdef enum { FOO2, BAR2, FOOBAR2 } default=BAR2
+onechoice enum { ONLYFOO } default=ONLYFOO
+refval reference
+refwithdef reference default=":parent:"
+fileVal file
+pathVal path
+
+boolarr[] bool
+intarr[] int
+longarr[] long
+doublearr[] double
+stringarr[] string
+enumarr[] enum { ARRAY, VALUES }
+refarr[] reference
+fileArr[] file
+pathArr[] path
+
+intMap{} int
+stringMap{} string
+filemap{} file
+pathMap{} path
+
+# A basic struct
+basicStruct.foo string default="basic"
+basicStruct.bar int
+basicStruct.intArr[] int
+
+# A struct of struct
+rootStruct.inner0.name string default="inner0"
+rootStruct.inner0.index int
+rootStruct.inner1.name string default="inner1"
+rootStruct.inner1.index int
+rootStruct.innerArr[].boolVal bool default=false
+rootStruct.innerArr[].stringVal string
+
+myarray[].intval int default=14
+myarray[].stringval[] string
+myarray[].enumval enum { INNER, ENUM, TYPE } default=TYPE
+myarray[].refval reference # Value in array without default
+myarray[].fileVal file
+myarray[].anotherarray[].foo int default=-4
+myarray[].myStruct.a int
+myarray[].myStruct.b int default=2
+
+myStructMap{}.myInt int
+myStructMap{}.myString string
+myStructMap{}.myIntDef int default=56
+myStructMap{}.myStringDef string default="g"
+myStructMap{}.anotherMap{}.anInt int
+myStructMap{}.anotherMap{}.anIntDef int default=11
diff --git a/config/src/test/resources/configs/def-files/int.def b/config/src/test/resources/configs/def-files/int.def
new file mode 100755
index 00000000000..fd07c90dfd5
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/int.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+version=1
+
+intVal int default=1
diff --git a/config/src/test/resources/configs/def-files/maptypes.def b/config/src/test/resources/configs/def-files/maptypes.def
new file mode 100644
index 00000000000..389a9b71012
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/maptypes.def
@@ -0,0 +1,13 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only structs in various forms
+namespace=foo
+
+boolmap{} bool
+intmap{} int
+longmap{} long
+doublemap{} double
+stringmap{} string
+filemap{} file
+
+innermap{}.foo int
+nestedmap{}.inner{} int
diff --git a/config/src/test/resources/configs/def-files/md5test.def b/config/src/test/resources/configs/def-files/md5test.def
new file mode 100644
index 00000000000..f9fd7a645d2
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/md5test.def
@@ -0,0 +1,27 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+# version=4 , version in comment does not count.
+
+# Added empty line to see if we can confuse
+# the server's md5 calculation
+version=3
+
+#even adding a variable name starting with 'version'
+versiontag int default=3
+
+blabla string default=""
+tabs string default=" "
+test int
+
+# test multiple spaces/tabs
+spaces int
+singletab string
+multitabs double
+
+# test enum
+normal enum { VAL1, VAL2 } default=VAL1
+spacevalues enum { V1 , V2 , V3 , V4 } default=V3
+
+# Comments and empty lines at the end
+
+
diff --git a/config/src/test/resources/configs/def-files/namespace.def b/config/src/test/resources/configs/def-files/namespace.def
new file mode 100644
index 00000000000..429b835a6aa
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/namespace.def
@@ -0,0 +1,6 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+
+namespace=myproject.config
+
+a int
diff --git a/config/src/test/resources/configs/def-files/simpletypes.def b/config/src/test/resources/configs/def-files/simpletypes.def
new file mode 100644
index 00000000000..e3954a92edc
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/simpletypes.def
@@ -0,0 +1,12 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+# Config containing only simple leaf types with default values, that can be used
+# for testing individual types in detail.
+version=1
+
+boolval bool default=false
+doubleval double default=0.0
+enumval enum { VAL1, VAL2 } default=VAL1
+intval int default=0
+longval long default=0
+stringval string default="s"
diff --git a/config/src/test/resources/configs/def-files/specialtypes.def b/config/src/test/resources/configs/def-files/specialtypes.def
new file mode 100644
index 00000000000..7b9b68d38a6
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/specialtypes.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+myfile file
+myref reference
diff --git a/config/src/test/resources/configs/def-files/standard.def b/config/src/test/resources/configs/def-files/standard.def
new file mode 100644
index 00000000000..94e2e3133f4
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/standard.def
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only simple leaf types with default values, that can be used
+# for testing individual types in detail.
+namespace=foo
+version=1
+
+basicStruct.intVal int default=0
+basicStruct.stringVal string default="s"
+stringArr[] string
diff --git a/config/src/test/resources/configs/def-files/string.def b/config/src/test/resources/configs/def-files/string.def
new file mode 100755
index 00000000000..0605dc6e10c
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/string.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+version=1
+
+stringVal string default="_default_"
diff --git a/config/src/test/resources/configs/def-files/structtypes.def b/config/src/test/resources/configs/def-files/structtypes.def
new file mode 100644
index 00000000000..dfcec942799
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/structtypes.def
@@ -0,0 +1,22 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only structs in various forms
+namespace=foo
+version=2
+
+simple.name string default="_default_"
+simple.gender enum { MALE, FEMALE } default=MALE
+simple.emails[] string
+
+nested.inner.name string default="_default_"
+nested.inner.gender enum { MALE, FEMALE } default=MALE
+nested.inner.emails[] string
+
+simplearr[].name string
+simplearr[].gender enum { MALE, FEMALE }
+
+nestedarr[].inner.name string
+nestedarr[].inner.gender enum { MALE, FEMALE }
+nestedarr[].inner.emails[] string
+
+complexarr[].innerarr[].name string
+complexarr[].innerarr[].gender enum { MALE, FEMALE }
diff --git a/config/src/test/resources/configs/def-files/test-nodefs.def b/config/src/test/resources/configs/def-files/test-nodefs.def
new file mode 100644
index 00000000000..e35438a26ff
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/test-nodefs.def
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=2
+namespace=foo
+
+# test config vars with no defaults
+
+s string
+j int
+b bool
+f double
+e enum { AA, BB, CC }
+
+basicstruct.foo string
+basicstruct.bar int
+
+arr[].s string
+arr[].i int
diff --git a/config/src/test/resources/configs/def-files/test-nonstring.def b/config/src/test/resources/configs/def-files/test-nonstring.def
new file mode 100644
index 00000000000..4819b32e1ba
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/test-nonstring.def
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+namespace=foo
+
+# Test non-string config vars with defaults
+
+i int default=0
+b bool default=false
+d double default=0.0
+e enum { AA, BB, CC } default=AA
diff --git a/config/src/test/resources/configs/def-files/test-reference.def b/config/src/test/resources/configs/def-files/test-reference.def
new file mode 100644
index 00000000000..dacb9a18544
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/test-reference.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+namespace=foo
+
+configId reference default=":parent:"
diff --git a/config/src/test/resources/configs/def-files/testnamespace.def b/config/src/test/resources/configs/def-files/testnamespace.def
new file mode 100644
index 00000000000..56710807954
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/testnamespace.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+namespace=foo
+basicStruct.stringVal string
diff --git a/config/src/test/resources/configs/def-files/unicode.def b/config/src/test/resources/configs/def-files/unicode.def
new file mode 100644
index 00000000000..310564f4cfd
--- /dev/null
+++ b/config/src/test/resources/configs/def-files/unicode.def
@@ -0,0 +1,6 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=2
+namespace=foo
+
+unicodestring1 string
+unicodestring2 string default="abc æøå 囲碁 ÆØÅ ABC"
diff --git a/config/src/test/resources/configs/foo/app.cfg b/config/src/test/resources/configs/foo/app.cfg
new file mode 100644
index 00000000000..30426957db6
--- /dev/null
+++ b/config/src/test/resources/configs/foo/app.cfg
@@ -0,0 +1,6 @@
+message "msg1"
+times 3
+a[3]
+a[0].name "a0"
+a[1].name "a1"
+a[2].name "a2"
diff --git a/config/src/test/resources/configs/foo/test-reference.cfg b/config/src/test/resources/configs/foo/test-reference.cfg
new file mode 100644
index 00000000000..127d63f27dc
--- /dev/null
+++ b/config/src/test/resources/configs/foo/test-reference.cfg
@@ -0,0 +1 @@
+configId ":parent:"
diff --git a/config/src/test/resources/configs/function-test/defaultvalues.txt b/config/src/test/resources/configs/function-test/defaultvalues.txt
new file mode 100644
index 00000000000..14a7de7a9ed
--- /dev/null
+++ b/config/src/test/resources/configs/function-test/defaultvalues.txt
@@ -0,0 +1,47 @@
+bool_val false
+int_val 5
+long_val 1234567890123
+double_val 41.23
+string_val "foo"
+enum_val FOOBAR
+refval :parent:
+fileVal "vespa.log"
+pathVal "pom.xml"
+boolarr[1]
+boolarr[0] false
+intarr[0]
+longarr[0]
+doublearr[2]
+doublearr[0] 2344
+doublearr[1] 123
+stringarr[1]
+stringarr[0] "bar"
+enumarr[1]
+enumarr[0] VALUES
+refarr[0]
+fileArr[0]
+
+basicStruct.bar 3
+basicStruct.intArr[1]
+basicStruct.intArr[0] 10
+rootStruct.inner0.index 11
+rootStruct.inner1.index 12
+rootStruct.innerArr[1]
+rootStruct.innerArr[0].stringVal "deep"
+
+myarray[2]
+myarray[0].stringval[2]
+myarray[0].stringval[0] "baah"
+myarray[0].stringval[1] "yikes"
+myarray[0].refval ":parent:"
+myarray[0].fileVal "command.com"
+myarray[0].anotherarray[1]
+myarray[0].anotherarray[0].foo 7
+myarray[0].myStruct.a 1
+myarray[1].stringval[0]
+myarray[1].refval ":parent:"
+myarray[1].fileVal "display.sys"
+myarray[1].anotherarray[2]
+myarray[1].anotherarray[0].foo 1
+myarray[1].anotherarray[1].foo 2
+myarray[1].myStruct.a -1
diff --git a/config/src/test/resources/configs/function-test/defaultvalues.xml b/config/src/test/resources/configs/function-test/defaultvalues.xml
new file mode 100644
index 00000000000..80644c2fdb1
--- /dev/null
+++ b/config/src/test/resources/configs/function-test/defaultvalues.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<config name="function-test">
+ <bool_val>false</bool_val>
+ <int_val>5</int_val>
+ <long_val>1234567890123</long_val>
+ <double_val>41.23</double_val>
+ <string_val>foo</string_val>
+ <enum_val>FOOBAR</enum_val>
+ <refval>:parent:</refval>
+ <fileVal>vespa.log</fileVal>
+
+ <boolarr index="0">false</boolarr>
+ <doublearr index="0">2344</doublearr>
+ <doublearr index="1">123</doublearr>
+ <stringarr index="0">bar</stringarr>
+ <enumarr index="0">VALUES</enumarr>
+
+ <basicStruct>
+ <bar>3</bar>
+ <intArr index="0">10</intArr>
+ </basicStruct>
+
+ <rootStruct>
+ <inner0>
+ <index>11</index>
+ </inner0>
+ <inner1>
+ <index>12</index>
+ </inner1>
+ <innerArr index="0">
+ <stringVal>deep</stringVal>
+ </innerArr>
+ </rootStruct>
+
+ <myarray index="0">
+ <stringval index="0">baah</stringval>
+ <stringval index="1">yikes</stringval>
+ <refval>:parent:</refval>
+ <fileVal>command.com</fileVal>
+ <anotherarray index="0">
+ <foo>7</foo>
+ </anotherarray>
+ <myStruct>
+ <a>1</a>
+ </myStruct>
+ </myarray>
+ <myarray index="1">
+ <refval>:parent:</refval>
+ <fileVal>display.sys</fileVal>
+ <anotherarray index="0">
+ <foo>1</foo>
+ </anotherarray>
+ <anotherarray index="1">
+ <foo>2</foo>
+ </anotherarray>
+ <myStruct>
+ <a>-1</a>
+ </myStruct>
+ </myarray>
+
+</config>
diff --git a/config/src/test/resources/configs/function-test/missingvalue.txt b/config/src/test/resources/configs/function-test/missingvalue.txt
new file mode 100644
index 00000000000..9616ac3d27b
--- /dev/null
+++ b/config/src/test/resources/configs/function-test/missingvalue.txt
@@ -0,0 +1,39 @@
+bool_val false
+long_val 123
+double_val 41.23
+string_val "foo"
+enum_val FOOBAR
+refval ":parent:"
+fileVal "msdos.sys"
+boolarr[1]
+boolarr[0] false
+intarr[0]
+longarr[0]
+doublearr[2]
+doublearr[0] 2344
+doublearr[1] 123
+stringarr[1]
+stringarr[0] "bar"
+enumarr[1]
+enumarr[0] VALUES
+refarr[0]
+basicStruct.bar 3
+rootStruct.inner0.index 11
+rootStruct.inner1.index 12
+rootStruct.innerArr[0]
+myarray[2]
+myarray[0].stringval[2]
+myarray[0].stringval[0] "baah"
+myarray[0].stringval[1] "yikes"
+myarray[0].refval ":parent:"
+myarray[0].fileVal "command.com"
+myarray[0].anotherarray[1]
+myarray[0].anotherarray[0].foo 7
+myarray[0].myStruct.a 1
+myarray[1].stringval[0]
+myarray[1].refval ":parent:"
+myarray[1].fileVal "display.sys"
+myarray[1].anotherarray[2]
+myarray[1].anotherarray[0].foo 1
+myarray[1].anotherarray[1].foo 2
+myarray[1].myStruct.a 1
diff --git a/config/src/test/resources/configs/function-test/randomorder.txt b/config/src/test/resources/configs/function-test/randomorder.txt
new file mode 100644
index 00000000000..bd91c24f3fb
--- /dev/null
+++ b/config/src/test/resources/configs/function-test/randomorder.txt
@@ -0,0 +1,56 @@
+boolarr[1]
+boolarr[0] false
+int_with_def -14
+double_val 41.23
+double_with_def -12
+enumwithdef BAR2
+refval ":parent:"
+refwithdef ":parent:"
+intarr[0]
+basicStruct.intArr[1]
+basicStruct.intArr[0] 10
+doublearr[2]
+doublearr[0] 2344
+doublearr[1] 123
+string_val "foo"
+stringwithdef "bar and foo"
+enum_val FOOBAR
+stringarr[1]
+stringarr[0] "bar"
+basicStruct.bar 3
+long_with_def -333000333000
+enumarr[1]
+enumarr[0] VALUES
+refarr[0]
+rootStruct.innerArr[1]
+rootStruct.innerArr[0].stringVal "deep"
+fileVal "autoexec.bat"
+myarray[2]
+myarray[0].intval -5
+myarray[0].myStruct.a 1
+myarray[0].enumval INNER
+myarray[0].refval ":parent:"
+myarray[0].anotherarray[1]
+myarray[0].anotherarray[0].foo 7
+myarray[0].stringval[2]
+myarray[0].stringval[0] "baah"
+myarray[0].stringval[1] "yikes"
+myarray[0].fileVal "file0"
+myarray[1].stringval[0]
+myarray[1].enumval INNER
+myarray[1].anotherarray[2]
+myarray[1].anotherarray[0].foo 1
+myarray[1].anotherarray[1].foo 2
+myarray[1].myStruct.a -1
+myarray[1].refval ":parent:"
+myarray[1].intval 5
+myarray[1].fileVal "file1"
+bool_val false
+bool_with_def true
+longarr[0]
+rootStruct.inner1.index 12
+int_val 5
+rootStruct.inner0.index 11
+long_val 666000666000
+fileArr[0]
+pathVal "pom.xml" \ No newline at end of file
diff --git a/config/src/test/resources/configs/function-test/variableaccess.txt b/config/src/test/resources/configs/function-test/variableaccess.txt
new file mode 100644
index 00000000000..997de21750d
--- /dev/null
+++ b/config/src/test/resources/configs/function-test/variableaccess.txt
@@ -0,0 +1,85 @@
+pathMap{"one"} "pom.xml"
+bool_val false
+bool_with_def true
+int_val 5
+int_with_def -14
+long_val 12345678901
+long_with_def -9876543210
+double_val 41.23
+double_with_def -12
+string_val "foo"
+stringwithdef "bar and foo"
+enum_val FOOBAR
+enumwithdef BAR2
+refval :parent:
+refwithdef ":parent:"
+fileVal "etc"
+pathVal "pom.xml"
+boolarr[1]
+boolarr[0] false
+intarr[0]
+longarr[2]
+longarr[0] 9223372036854775807
+longarr[1] -9223372036854775808
+doublearr[2]
+doublearr[0] 2344
+doublearr[1] 123
+stringarr[1]
+stringarr[0] "bar"
+enumarr[1]
+enumarr[0] VALUES
+refarr[3]
+refarr[0] ":parent:"
+refarr[1] ":parent"
+refarr[2] "parent:"
+fileArr[1]
+fileArr[0] "bin"
+pathArr[1]
+pathArr[0] "pom.xml"
+
+intMap{"one"} 1
+intMap{"two"} 2
+stringMap{"one"} "first"
+
+basicStruct.foo "basicFoo"
+basicStruct.bar 3
+basicStruct.intArr[2]
+basicStruct.intArr[0] 310
+basicStruct.intArr[1] 311
+rootStruct.inner0.index 11
+rootStruct.inner1.index 12
+rootStruct.innerArr[2]
+rootStruct.innerArr[0].boolVal true
+rootStruct.innerArr[0].stringVal "deep"
+rootStruct.innerArr[1].boolVal false
+rootStruct.innerArr[1].stringVal "blue a=\"escaped\""
+
+myarray[2]
+myarray[0].intval -5
+myarray[0].stringval[2]
+myarray[0].stringval[0] "baah"
+myarray[0].stringval[1] "yikes"
+myarray[0].enumval INNER
+myarray[0].refval :parent:
+myarray[0].fileVal "file0"
+myarray[0].anotherarray[1]
+myarray[0].anotherarray[0].foo 7
+myarray[0].myStruct.a 1
+myarray[0].myStruct.b 2
+myarray[1].intval 5
+myarray[1].stringval[0]
+myarray[1].enumval INNER
+myarray[1].refval ":parent:"
+myarray[1].fileVal "file1"
+myarray[1].anotherarray[2]
+myarray[1].anotherarray[0].foo 1
+myarray[1].anotherarray[1].foo 2
+myarray[1].myStruct.a -1
+myarray[1].myStruct.b -2
+
+myStructMap{"one"}.myInt 1
+myStructMap{"one"}.myString "bull"
+myStructMap{"one"}.myIntDef 2
+myStructMap{"one"}.myStringDef "bear"
+myStructMap{"one"}.anotherMap{"anotherOne"}.anInt 3
+myStructMap{"one"}.anotherMap{"anotherOne"}.anIntDef 4
diff --git a/config/src/test/resources/configs/illegal/app.cfg b/config/src/test/resources/configs/illegal/app.cfg
new file mode 100644
index 00000000000..d22d3d46c90
--- /dev/null
+++ b/config/src/test/resources/configs/illegal/app.cfg
@@ -0,0 +1,6 @@
+message "msg1"
+times thisisnotanint
+a[3]
+a[0].name "a0"
+a[1].name "a1"
+a[2].name "a2"
diff --git a/config/src/test/resources/configs/unicode/unicode.cfg b/config/src/test/resources/configs/unicode/unicode.cfg
new file mode 100644
index 00000000000..d2c94abea86
--- /dev/null
+++ b/config/src/test/resources/configs/unicode/unicode.cfg
@@ -0,0 +1,2 @@
+unicodestring1 "Hei æøå 바둑 ÆØÅ hallo"
+unicodestring2 "abc æøå 囲碁 ÆØÅ ABC"