summaryrefslogtreecommitdiffstats
path: root/container-di/src/test
diff options
context:
space:
mode:
authorHarald Musum <musum@verizonmedia.com>2021-04-06 14:19:35 +0200
committerGitHub <noreply@github.com>2021-04-06 14:19:35 +0200
commit778894b29b13831115c19ff13285541a10ab2d30 (patch)
tree62ea7aa8689a3d5421f54cd0ac6c5290e82f23dc /container-di/src/test
parent5df00bb90a04082847440716bcb6146bdda0ca06 (diff)
Revert "Gjoranv/merge di into core (rebased)"
Diffstat (limited to 'container-di/src/test')
-rw-r--r--container-di/src/test/java/com/yahoo/component/ComponentSpecTestCase.java83
-rw-r--r--container-di/src/test/java/com/yahoo/component/provider/test/ComponentRegistryTestCase.java94
-rw-r--r--container-di/src/test/java/com/yahoo/component/test/ComponentIdTestCase.java43
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ConfigRetrieverTest.java121
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTest.java408
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java125
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/DirConfigSource.java69
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java750
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/core/FallbackToGuiceInjectorTest.java151
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java68
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.java254
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java85
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java67
-rw-r--r--container-di/src/test/vespa-configdef/config.di.int.def5
-rw-r--r--container-di/src/test/vespa-configdef/config.di.string.def5
-rw-r--r--container-di/src/test/vespa-configdef/config.test.bootstrap1.def4
-rw-r--r--container-di/src/test/vespa-configdef/config.test.bootstrap2.def5
-rw-r--r--container-di/src/test/vespa-configdef/config.test.components1.def4
-rw-r--r--container-di/src/test/vespa-configdef/config.test.test.def5
-rw-r--r--container-di/src/test/vespa-configdef/config.test.test2.def5
-rw-r--r--container-di/src/test/vespa-configdef/config.test.thread-pool.def5
21 files changed, 2356 insertions, 0 deletions
diff --git a/container-di/src/test/java/com/yahoo/component/ComponentSpecTestCase.java b/container-di/src/test/java/com/yahoo/component/ComponentSpecTestCase.java
new file mode 100644
index 00000000000..6fe58e99fda
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/component/ComponentSpecTestCase.java
@@ -0,0 +1,83 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.component;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Arne Bergene Fossaa
+ */
+public class ComponentSpecTestCase {
+
+ @Test
+ public void testMatches() {
+ ComponentId a = new ComponentId("test:1");
+ ComponentId b = new ComponentId("test:1.1.1");
+ ComponentId c = new ComponentId("test:2");
+ ComponentId d = new ComponentId("test:3");
+ ComponentId e = new ComponentId("test");
+
+ ComponentSpecification aspec = new ComponentSpecification("test");
+ ComponentSpecification bspec = new ComponentSpecification("test:1");
+ ComponentSpecification cspec = new ComponentSpecification("test:2");
+ ComponentSpecification dspec = new ComponentSpecification("test1:2");
+
+ assertTrue(aspec.matches(a));
+ assertTrue(aspec.matches(b));
+ assertTrue(aspec.matches(c));
+ assertTrue(aspec.matches(d));
+ assertTrue(aspec.matches(e));
+
+ assertTrue(bspec.matches(a));
+ assertTrue(bspec.matches(b));
+ assertFalse(bspec.matches(c));
+ assertFalse(bspec.matches(d));
+ assertFalse(bspec.matches(e));
+
+ assertFalse(cspec.matches(a));
+ assertFalse(cspec.matches(b));
+ assertTrue(cspec.matches(c));
+ assertFalse(cspec.matches(d));
+ assertFalse(cspec.matches(e));
+
+ assertFalse(dspec.matches(a));
+ assertFalse(dspec.matches(b));
+ assertFalse(dspec.matches(c));
+ assertFalse(dspec.matches(d));
+ assertFalse(dspec.matches(e));
+
+ }
+
+ @Test
+ public void testMatchesWithNamespace() {
+ ComponentId namespace = new ComponentId("namespace:2");
+
+ ComponentId a = new ComponentId("test", new Version(1), namespace);
+ ComponentId b = new ComponentId("test:1@namespace:2");
+ ComponentId c = new ComponentId("test:1@namespace");
+ assertEquals(a, b);
+ assertFalse(a.equals(c));
+
+ ComponentSpecification spec = new ComponentSpecification("test", null, namespace);
+ assertTrue(spec.matches(a));
+ assertTrue(spec.matches(b));
+ assertFalse(spec.matches(c));
+ }
+
+ @Test
+ public void testStringValue() {
+ assertStringValueEqualsInputSpec("a:1.0.0.alpha@namespace");
+ assertStringValueEqualsInputSpec("a:1.0.0.alpha");
+ assertStringValueEqualsInputSpec("a:1.0");
+ assertStringValueEqualsInputSpec("a");
+ }
+
+ private void assertStringValueEqualsInputSpec(String componentSpec) {
+ assertEquals(componentSpec,
+ new ComponentSpecification(componentSpec).stringValue());
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/component/provider/test/ComponentRegistryTestCase.java b/container-di/src/test/java/com/yahoo/component/provider/test/ComponentRegistryTestCase.java
new file mode 100644
index 00000000000..69eec95b746
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/component/provider/test/ComponentRegistryTestCase.java
@@ -0,0 +1,94 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.component.provider.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.ComponentSpecification;
+import com.yahoo.component.provider.ComponentRegistry;
+
+/**
+ * Tests that ComponentRegistry handles namespaces correctly.
+ *
+ * @author Tony Vaagenes
+ */
+public class ComponentRegistryTestCase {
+ private static class TestComponent extends AbstractComponent {
+ TestComponent(ComponentId componentId) {
+ super(componentId);
+ }
+ }
+
+ private static final String componentName = "component";
+
+ private static final String namespace1 = "namespace1";
+ private static final String namespace2 = "namespace2";
+ private static final String namespace21 = "namespace2:1";
+
+ private static final TestComponent component1 = componentInNamespace(namespace1);
+ private static final TestComponent component2 = componentInNamespace(namespace2);
+ private static final TestComponent component21 = componentInNamespace(namespace21);
+
+ private ComponentRegistry<TestComponent> registry;
+
+ private static ComponentSpecification specInNamespace(String namespace) {
+ return new ComponentSpecification(componentName + "@" + namespace);
+ }
+
+ private static ComponentId idInNamespace(String namespace) {
+ return specInNamespace(namespace).toId();
+ }
+
+ private static TestComponent componentInNamespace(String namespace) {
+ return new TestComponent(idInNamespace(namespace));
+ }
+
+ @Before
+ public void before() {
+ registry = new ComponentRegistry<>();
+
+ registry.register(component1.getId(), component1);
+ registry.register(component2.getId(), component2);
+ registry.register(component21.getId(), component21);
+ }
+
+ @Test
+ public void testAllPresent() {
+ assertEquals(3, registry.getComponentCount());
+ }
+
+ @Test
+ public void testIdNamespaceLookup() {
+ assertEquals(component1, registry.getComponent(idInNamespace(namespace1)));
+ assertEquals(component2, registry.getComponent(idInNamespace(namespace2)));
+ assertEquals(component21, registry.getComponent(idInNamespace(namespace21)));
+ }
+
+ @Test
+ public void testSpecNamespaceLookup() {
+ assertEquals(component1, registry.getComponent(specInNamespace(namespace1)));
+
+ // Version for namespace must match the specification exactly, so do not return version '1' when an
+ // empty version is asked for.
+ assertEquals(component2, registry.getComponent(specInNamespace(namespace2)));
+ assertEquals(component21, registry.getComponent(specInNamespace(namespace21)));
+ }
+
+ @Test
+ public void testInnerComponentNotMixedWithTopLevelComponent() {
+ assertNull(registry.getComponent(componentName));
+
+ TestComponent topLevel = new TestComponent(new ComponentId(componentName));
+ registry.register(topLevel.getId(), topLevel);
+ assertEquals(topLevel, registry.getComponent(componentName));
+
+ assertEquals(component1, registry.getComponent(specInNamespace(namespace1)));
+ assertEquals(component1, registry.getComponent(idInNamespace(namespace1)));
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/component/test/ComponentIdTestCase.java b/container-di/src/test/java/com/yahoo/component/test/ComponentIdTestCase.java
new file mode 100644
index 00000000000..0842ee4a797
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/component/test/ComponentIdTestCase.java
@@ -0,0 +1,43 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.component.test;
+
+import com.yahoo.component.ComponentId;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bratseth
+ */
+public class ComponentIdTestCase {
+
+ @Test
+ public void testFileNameConversion() {
+ assertFileNameEquals("a","a");
+ assertFileNameEquals("a-1","a-1");
+ assertFileNameEquals("a-1","a-1.0.0");
+ assertFileNameEquals("a-1.0.0.qualifier","a-1.0.0.qualifier");
+ assertFileNameEquals("a.b-1.0.0.qualifier","a.b-1.0.0.qualifier");
+ assertFileNameEquals("a@space","a@space");
+ assertFileNameEquals("a-1@space","a-1@space");
+ assertFileNameEquals("a-1@space","a-1.0.0@space");
+ assertFileNameEquals("a-1.0.0.qualifier@space","a-1.0.0.qualifier@space");
+ assertFileNameEquals("a.b-1.0.0.qualifier@space","a.b-1.0.0.qualifier@space");
+ }
+
+ /** Takes two id file names as input */
+ private void assertFileNameEquals(String expected,String initial) {
+ assertEquals("'" + initial + "' became id '" + ComponentId.fromFileName(initial) + "' which should become '" + expected + "'",
+ expected,ComponentId.fromFileName(initial).toFileName());
+ }
+
+ @Test
+ public void testCompareWithNameSpace() {
+ ComponentId withNS = ComponentId.fromString("foo@ns");
+ ComponentId withoutNS = ComponentId.fromString("foo"); // Should be less than withNs
+
+ assertEquals(withNS.compareTo(withoutNS), 1);
+ assertEquals(withoutNS.compareTo(withNS), -1);
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/ConfigRetrieverTest.java b/container-di/src/test/java/com/yahoo/container/di/ConfigRetrieverTest.java
new file mode 100644
index 00000000000..290836d7842
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/ConfigRetrieverTest.java
@@ -0,0 +1,121 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.test.Bootstrap1Config;
+import com.yahoo.config.test.Bootstrap2Config;
+import com.yahoo.config.test.TestConfig;
+import com.yahoo.container.di.ConfigRetriever.BootstrapConfigs;
+import com.yahoo.container.di.ConfigRetriever.ComponentsConfigs;
+import com.yahoo.container.di.ConfigRetriever.ConfigSnapshot;
+import com.yahoo.vespa.config.ConfigKey;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * @author gjoranv
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ConfigRetrieverTest {
+
+ private DirConfigSource dirConfigSource = null;
+
+ @Before
+ public void setup() {
+ dirConfigSource = new DirConfigSource("ConfigRetrieverTest-");
+ }
+
+ @After
+ public void cleanup() {
+ dirConfigSource.cleanup();
+ }
+
+ @Test
+ public void require_that_bootstrap_configs_come_first() {
+ writeConfigs();
+ ConfigRetriever retriever = createConfigRetriever();
+ ConfigSnapshot bootstrapConfigs = retriever.getConfigs(Collections.emptySet(), 0, true);
+
+ assertThat(bootstrapConfigs, Matchers.instanceOf(BootstrapConfigs.class));
+ }
+
+ @Test
+ @SuppressWarnings("unused")
+ public void require_that_components_comes_after_bootstrap() {
+ writeConfigs();
+ ConfigRetriever retriever = createConfigRetriever();
+ ConfigSnapshot bootstrapConfigs = retriever.getConfigs(Collections.emptySet(), 0, true);
+
+ ConfigKey<? extends ConfigInstance> testConfigKey = new ConfigKey<>(TestConfig.class, dirConfigSource.configId());
+ ConfigSnapshot componentsConfigs = retriever.getConfigs(Collections.singleton(testConfigKey), 0, true);
+
+ if (componentsConfigs instanceof ComponentsConfigs) {
+ assertThat(componentsConfigs.size(), is(3));
+ } else {
+ fail("ComponentsConfigs has unexpected type: " + componentsConfigs);
+ }
+ }
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Ignore
+ @SuppressWarnings("unused")
+ public void require_exception_upon_modified_components_keys_without_bootstrap() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ writeConfigs();
+ ConfigRetriever retriever = createConfigRetriever();
+ ConfigKey<? extends ConfigInstance> testConfigKey = new ConfigKey<>(TestConfig.class, dirConfigSource.configId());
+ ConfigSnapshot bootstrapConfigs = retriever.getConfigs(Collections.emptySet(), 0, true);
+ ConfigSnapshot componentsConfigs = retriever.getConfigs(Collections.singleton(testConfigKey), 0, true);
+ Set<ConfigKey<? extends ConfigInstance>> keys = new HashSet<>();
+ keys.add(testConfigKey);
+ keys.add(new ConfigKey<>(TestConfig.class, ""));
+ retriever.getConfigs(keys, 0, true);
+ }
+
+ @Test
+ public void require_that_empty_components_keys_after_bootstrap_returns_components_configs() {
+ writeConfigs();
+ ConfigRetriever retriever = createConfigRetriever();
+ assertThat(retriever.getConfigs(Collections.emptySet(), 0, true), instanceOf(BootstrapConfigs.class));
+ assertThat(retriever.getConfigs(Collections.emptySet(), 0, true), instanceOf(ComponentsConfigs.class));
+ }
+
+ public void writeConfigs() {
+ writeConfig("bootstrap1", "dummy \"ignored\"");
+ writeConfig("bootstrap2", "dummy \"ignored\"");
+ writeConfig("test", "stringVal \"ignored\"");
+ }
+
+ private ConfigRetriever createConfigRetriever() {
+ String configId = dirConfigSource.configId();
+ CloudSubscriberFactory subscriber = new CloudSubscriberFactory(dirConfigSource.configSource());
+ Set<ConfigKey<? extends ConfigInstance>> keys = new HashSet<>();
+ keys.add(new ConfigKey<>(Bootstrap1Config.class, configId));
+ keys.add(new ConfigKey<>(Bootstrap2Config.class, configId));
+ return new ConfigRetriever(keys, keySet -> subscriber.getSubscriber(keySet));
+ }
+
+ private void writeConfig(String name, String contents) {
+ dirConfigSource.writeConfig(name, contents);
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
new file mode 100644
index 00000000000..b596246a43d
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
@@ -0,0 +1,408 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di;
+
+import com.google.inject.Guice;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.config.di.IntConfig;
+import com.yahoo.config.test.TestConfig;
+import com.yahoo.container.bundle.MockBundle;
+import com.yahoo.container.di.componentgraph.Provider;
+import com.yahoo.container.di.componentgraph.core.ComponentGraph;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent2;
+import com.yahoo.container.di.componentgraph.core.ComponentNode.ComponentConstructorException;
+import com.yahoo.container.di.config.RestApiContext;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Tony Vaagenes
+ * @author gjoranv
+ * @author ollivir
+ */
+public class ContainerTest extends ContainerTestBase {
+
+ @Test
+ public void components_can_be_created_from_config() {
+ writeBootstrapConfigs();
+ dirConfigSource.writeConfig("test", "stringVal \"myString\"");
+
+ Container container = newContainer(dirConfigSource);
+
+ ComponentTakingConfig component = createComponentTakingConfig(getNewComponentGraph(container));
+ assertEquals("myString", component.config.stringVal());
+
+ container.shutdownConfigurer();
+ }
+
+ @Test
+ public void components_are_reconfigured_after_config_update_without_bootstrap_configs() {
+ writeBootstrapConfigs();
+ dirConfigSource.writeConfig("test", "stringVal \"original\"");
+
+ Container container = newContainer(dirConfigSource);
+ ComponentGraph componentGraph = getNewComponentGraph(container);
+ ComponentTakingConfig component = createComponentTakingConfig(componentGraph);
+
+ assertEquals("original", component.config.stringVal());
+
+ // Reconfigure
+ dirConfigSource.writeConfig("test", "stringVal \"reconfigured\"");
+ container.reloadConfig(2);
+
+ ComponentGraph newComponentGraph = getNewComponentGraph(container, componentGraph);
+ ComponentTakingConfig component2 = createComponentTakingConfig(newComponentGraph);
+ assertEquals("reconfigured", component2.config.stringVal());
+
+ container.shutdownConfigurer();
+ }
+
+ @Test
+ public void graph_is_updated_after_bootstrap_update() {
+ dirConfigSource.writeConfig("test", "stringVal \"original\"");
+ writeBootstrapConfigs("id1");
+
+ Container container = newContainer(dirConfigSource);
+
+ ComponentGraph graph = getNewComponentGraph(container);
+ ComponentTakingConfig component = createComponentTakingConfig(graph);
+ assertEquals("id1", component.getId().toString());
+
+ writeBootstrapConfigs(
+ new ComponentEntry("id1", ComponentTakingConfig.class),
+ new ComponentEntry("id2", ComponentTakingConfig.class));
+
+ container.reloadConfig(2);
+ ComponentGraph newGraph = getNewComponentGraph(container, graph);
+
+ assertNotNull(ComponentGraph.getNode(newGraph, "id1"));
+ assertNotNull(ComponentGraph.getNode(newGraph, "id2"));
+
+ container.shutdownConfigurer();
+ }
+
+ //@Test TODO
+ public void deconstructor_is_given_guice_components() {
+ }
+
+ @Test
+ public void component_is_deconstructed_when_not_reused() {
+ writeBootstrapConfigs("id1", DestructableComponent.class);
+
+ Container container = newContainer(dirConfigSource);
+
+ ComponentGraph oldGraph = getNewComponentGraph(container);
+ DestructableComponent componentToDestruct = oldGraph.getInstance(DestructableComponent.class);
+
+ writeBootstrapConfigs("id2", DestructableComponent.class);
+ container.reloadConfig(2);
+ getNewComponentGraph(container, oldGraph);
+ assertTrue(componentToDestruct.deconstructed);
+ }
+
+ @Ignore // because logAndDie is impossible(?) to verify programmatically
+ @Test
+ public void manually_verify_what_happens_when_first_graph_contains_component_that_throws_exception_in_ctor() {
+ writeBootstrapConfigs("thrower", ComponentThrowingExceptionInConstructor.class);
+ Container container = newContainer(dirConfigSource);
+ try {
+ getNewComponentGraph(container);
+ fail("Expected to log and die.");
+ } catch (Throwable t) {
+ fail("Expected to log and die");
+ }
+ }
+
+ @Test
+ public void previous_graph_is_retained_when_new_graph_contains_component_that_throws_exception_in_ctor() {
+ ComponentEntry simpleComponentEntry = new ComponentEntry("simpleComponent", SimpleComponent.class);
+
+ writeBootstrapConfigs(simpleComponentEntry);
+ Container container = newContainer(dirConfigSource);
+ ComponentGraph currentGraph = getNewComponentGraph(container);
+
+ SimpleComponent simpleComponent = currentGraph.getInstance(SimpleComponent.class);
+
+ writeBootstrapConfigs("thrower", ComponentThrowingExceptionInConstructor.class);
+ container.reloadConfig(2);
+ try {
+ currentGraph = getNewComponentGraph(container, currentGraph);
+ fail("Expected exception");
+ } catch (ComponentConstructorException ignored) {
+ // Expected, do nothing
+ } catch (Throwable t) {
+ fail("Expected ComponentConstructorException");
+ }
+ assertEquals(1, currentGraph.generation());
+
+ // Also verify that next reconfig is successful
+ ComponentEntry componentTakingConfigEntry = new ComponentEntry("componentTakingConfig", ComponentTakingConfig.class);
+ dirConfigSource.writeConfig("test", "stringVal \"myString\"");
+ writeBootstrapConfigs(simpleComponentEntry, componentTakingConfigEntry);
+ container.reloadConfig(3);
+ currentGraph = getNewComponentGraph(container, currentGraph);
+
+ assertEquals(3, currentGraph.generation());
+ assertSame(simpleComponent, currentGraph.getInstance(SimpleComponent.class));
+ assertNotNull(currentGraph.getInstance(ComponentTakingConfig.class));
+ }
+
+ @Test
+ public void previous_graph_is_retained_when_new_graph_throws_exception_for_missing_config() {
+ ComponentEntry simpleComponentEntry = new ComponentEntry("simpleComponent", SimpleComponent.class);
+
+ writeBootstrapConfigs(simpleComponentEntry);
+ Container container = newContainer(dirConfigSource);
+ ComponentGraph currentGraph = getNewComponentGraph(container);
+
+ currentGraph.getInstance(SimpleComponent.class);
+
+ writeBootstrapConfigs("thrower", ComponentThrowingExceptionForMissingConfig.class);
+ dirConfigSource.writeConfig("test", "stringVal \"myString\"");
+ container.reloadConfig(2);
+ try {
+ currentGraph = getNewComponentGraph(container, currentGraph);
+ fail("Expected exception");
+ } catch (IllegalArgumentException ignored) {
+ // Expected, do nothing
+ } catch (Throwable t) {
+ fail("Expected IllegalArgumentException");
+ }
+ assertEquals(1, currentGraph.generation());
+ }
+
+ @Test
+ public void getNewComponentGraph_hangs_waiting_for_valid_config_after_invalid_config() throws Exception {
+ dirConfigSource.writeConfig("test", "stringVal \"original\"");
+ writeBootstrapConfigs("myId", ComponentTakingConfig.class);
+
+ Container container = newContainer(dirConfigSource);
+ final ComponentGraph currentGraph = getNewComponentGraph(container);
+
+ writeBootstrapConfigs("thrower", ComponentThrowingExceptionForMissingConfig.class);
+ container.reloadConfig(2);
+
+ try {
+ getNewComponentGraph(container, currentGraph);
+ fail("expected exception");
+ } catch (Exception ignored) {
+ }
+ ExecutorService exec = Executors.newFixedThreadPool(1);
+ Future<ComponentGraph> newGraph = exec.submit(() -> getNewComponentGraph(container, currentGraph));
+
+ try {
+ newGraph.get(1, TimeUnit.SECONDS);
+ fail("Expected waiting for new config.");
+ } catch (Exception ignored) {
+ // expect to time out
+ }
+
+ writeBootstrapConfigs("myId2", ComponentTakingConfig.class);
+ container.reloadConfig(3);
+
+ assertNotNull(newGraph.get(5, TimeUnit.MINUTES));
+ }
+
+
+ @Test
+ public void bundle_info_is_set_on_rest_api_context() {
+ Class<RestApiContext> clazz = RestApiContext.class;
+
+ writeBootstrapConfigs("restApiContext", clazz);
+ dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
+ dirConfigSource.writeConfig("jersey-injection", "inject[0]");
+
+ Container container = newContainer(dirConfigSource);
+ ComponentGraph componentGraph = getNewComponentGraph(container);
+
+ RestApiContext restApiContext = componentGraph.getInstance(clazz);
+ assertNotNull(restApiContext);
+
+ assertEquals(1, restApiContext.getBundles().size());
+ assertEquals(MockBundle.SymbolicName, restApiContext.getBundles().get(0).symbolicName);
+ assertEquals(MockBundle.BundleVersion, restApiContext.getBundles().get(0).version);
+
+ container.shutdownConfigurer();
+ }
+
+ @Test
+ public void restApiContext_has_all_components_injected() {
+ Class<RestApiContext> restApiClass = RestApiContext.class;
+ Class<SimpleComponent> injectedClass = SimpleComponent.class;
+ String injectedComponentId = "injectedComponent";
+ Class<SimpleComponent2> anotherComponentClass = SimpleComponent2.class;
+ String anotherComponentId = "anotherComponent";
+
+ String componentsConfig =
+ new ComponentEntry(injectedComponentId, injectedClass).asConfig(0) + "\n" +
+ new ComponentEntry(anotherComponentId, anotherComponentClass).asConfig(1) + "\n" +
+ new ComponentEntry("restApiContext", restApiClass).asConfig(2) + "\n" +
+ "components[2].inject[0].id " + injectedComponentId + "\n" +
+ "components[2].inject[1].id " + anotherComponentId + "\n";
+
+ String injectionConfig = "inject[1]\n" +//
+ "inject[0].instance " + injectedComponentId + "\n" +//
+ "inject[0].forClass \"" + injectedClass.getName() + "\"\n";
+
+ dirConfigSource.writeConfig("components", componentsConfig);
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
+ dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
+ dirConfigSource.writeConfig("jersey-injection", injectionConfig);
+
+ Container container = newContainer(dirConfigSource);
+ ComponentGraph componentGraph = getNewComponentGraph(container);
+
+ RestApiContext restApiContext = componentGraph.getInstance(restApiClass);
+
+ assertFalse(restApiContext.getInjectableComponents().isEmpty());
+ assertEquals(2, restApiContext.getInjectableComponents().size());
+
+ container.shutdownConfigurer();
+ }
+
+ @Test
+ public void providers_are_destructed() {
+ writeBootstrapConfigs("id1", DestructableProvider.class);
+
+ ComponentDeconstructor deconstructor = (components, bundles) -> {
+ components.forEach(component -> {
+ if (component instanceof AbstractComponent) {
+ ((AbstractComponent) component).deconstruct();
+ } else if (component instanceof Provider) {
+ ((Provider<?>) component).deconstruct();
+ }
+ });
+ if (! bundles.isEmpty()) throw new IllegalArgumentException("This test should not use bundles");
+ };
+
+ Container container = newContainer(dirConfigSource, deconstructor);
+
+ ComponentGraph oldGraph = getNewComponentGraph(container);
+ DestructableEntity destructableEntity = oldGraph.getInstance(DestructableEntity.class);
+
+ writeBootstrapConfigs("id2", DestructableProvider.class);
+ container.reloadConfig(2);
+ getNewComponentGraph(container, oldGraph);
+
+ assertTrue(destructableEntity.deconstructed);
+ }
+
+ @Test
+ public void providers_are_invoked_only_when_needed() {
+ writeBootstrapConfigs("id1", FailOnGetProvider.class);
+
+ Container container = newContainer(dirConfigSource);
+
+ ComponentGraph oldGraph = getNewComponentGraph(container);
+ }
+
+ static class DestructableEntity {
+ private boolean deconstructed = false;
+ }
+
+ public static class DestructableProvider implements Provider<DestructableEntity> {
+ DestructableEntity instance = new DestructableEntity();
+
+ public DestructableEntity get() {
+ return instance;
+ }
+
+ public void deconstruct() {
+ assertFalse(instance.deconstructed);
+ instance.deconstructed = true;
+ }
+ }
+
+ public static class FailOnGetProvider implements Provider<Integer> {
+
+ public Integer get() {
+ fail("Should never be called.");
+ return null;
+ }
+
+ public void deconstruct() {
+ }
+
+ }
+
+ public static class ComponentTakingConfig extends AbstractComponent {
+ private final TestConfig config;
+
+ public ComponentTakingConfig(TestConfig config) {
+ assertNotNull(config);
+ this.config = config;
+ }
+ }
+
+ public static class ComponentThrowingExceptionInConstructor {
+ public ComponentThrowingExceptionInConstructor() {
+ throw new RuntimeException("This component fails upon construction.");
+ }
+ }
+
+ public static class ComponentThrowingExceptionForMissingConfig extends AbstractComponent {
+ public ComponentThrowingExceptionForMissingConfig(IntConfig intConfig) {
+ fail("This component should never be created. Only used for tests where 'int' config is missing.");
+ }
+ }
+
+ public static class DestructableComponent extends AbstractComponent {
+ private boolean deconstructed = false;
+
+ @Override
+ public void deconstruct() {
+ deconstructed = true;
+ }
+ }
+
+ public static class TestDeconstructor implements ComponentDeconstructor {
+ @Override
+ public void deconstruct(List<Object> components, Collection<Bundle> bundles) {
+ components.forEach(component -> {
+ if (component instanceof DestructableComponent) {
+ DestructableComponent vespaComponent = (DestructableComponent) component;
+ vespaComponent.deconstruct();
+ }
+ });
+ if (! bundles.isEmpty()) throw new IllegalArgumentException("This test should not use bundles");
+ }
+ }
+
+ private static Container newContainer(DirConfigSource dirConfigSource,
+ ComponentDeconstructor deconstructor) {
+ return new Container(new CloudSubscriberFactory(dirConfigSource.configSource), dirConfigSource.configId(), deconstructor);
+ }
+
+ private static Container newContainer(DirConfigSource dirConfigSource) {
+ return newContainer(dirConfigSource, new TestDeconstructor());
+ }
+
+ ComponentGraph getNewComponentGraph(Container container, ComponentGraph oldGraph) {
+ return container.getNewComponentGraph(oldGraph, Guice.createInjector(), true);
+ }
+
+ ComponentGraph getNewComponentGraph(Container container) {
+ return container.getNewComponentGraph(new ComponentGraph(), Guice.createInjector(), true);
+ }
+
+ private ComponentTakingConfig createComponentTakingConfig(ComponentGraph componentGraph) {
+ return componentGraph.getInstance(ComponentTakingConfig.class);
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
new file mode 100644
index 00000000000..2106a1f3671
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
@@ -0,0 +1,125 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di;
+
+import com.google.inject.Guice;
+import com.yahoo.component.ComponentSpecification;
+import com.yahoo.config.FileReference;
+import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.container.di.ContainerTest.ComponentTakingConfig;
+import com.yahoo.container.di.componentgraph.core.ComponentGraph;
+import com.yahoo.container.di.osgi.BundleClasses;
+import org.junit.After;
+import org.junit.Before;
+import org.osgi.framework.Bundle;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+
+/**
+ * @author Tony Vaagenes
+ * @author gjoranv
+ * @author ollivir
+ */
+public class ContainerTestBase {
+
+ private ComponentGraph componentGraph;
+ protected DirConfigSource dirConfigSource = null;
+
+ @Before
+ public void setup() {
+ dirConfigSource = new DirConfigSource("ContainerTest-");
+ }
+
+ @After
+ public void cleanup() {
+ dirConfigSource.cleanup();
+ }
+
+ @Before
+ public void createGraph() {
+ componentGraph = new ComponentGraph(0);
+ }
+
+ public void complete() {
+ try {
+ Container container = new Container(new CloudSubscriberFactory(dirConfigSource.configSource()), dirConfigSource.configId(),
+ new ContainerTest.TestDeconstructor(), new Osgi() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public Class<Object> resolveClass(BundleInstantiationSpecification spec) {
+ try {
+ return (Class<Object>) Class.forName(spec.classId.getName());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public BundleClasses getBundleClasses(ComponentSpecification bundle, Set<String> packagesToScan) {
+ throw new UnsupportedOperationException("getBundleClasses not supported");
+ }
+
+ @Override
+ public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
+ return emptySet();
+ }
+
+ @Override
+ public Bundle getBundle(ComponentSpecification spec) {
+ throw new UnsupportedOperationException("getBundle not supported.");
+ }
+ });
+ componentGraph = container.getNewComponentGraph(componentGraph, Guice.createInjector(), true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public <T> T getInstance(Class<T> componentClass) {
+ return componentGraph.getInstance(componentClass);
+ }
+
+ protected void writeBootstrapConfigs(ComponentEntry... componentEntries) {
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
+ StringBuilder components = new StringBuilder();
+ for (int i = 0; i < componentEntries.length; i++) {
+ components.append(componentEntries[i].asConfig(i));
+ components.append('\n');
+ }
+ dirConfigSource.writeConfig("components", String.format("components[%s]\n%s", componentEntries.length, components));
+ }
+
+ protected void writeBootstrapConfigs(String componentId, Class<?> classId) {
+ writeBootstrapConfigs(new ComponentEntry(componentId, classId));
+ }
+
+ protected void writeBootstrapConfigs(String componentId) {
+ writeBootstrapConfigs(componentId, ComponentTakingConfig.class);
+ }
+
+ protected void writeBootstrapConfigs() {
+ writeBootstrapConfigs(ComponentTakingConfig.class.getName(), ComponentTakingConfig.class);
+ }
+
+ protected class ComponentEntry {
+ private final String componentId;
+ private final Class<?> classId;
+
+ ComponentEntry(String componentId, Class<?> classId) {
+ this.componentId = componentId;
+ this.classId = classId;
+ }
+
+ String asConfig(int position) {
+ return "<config>\n" + //
+ "components[" + position + "].id \"" + componentId + "\"\n" + //
+ "components[" + position + "].classId \"" + classId.getName() + "\"\n" + //
+ "components[" + position + "].configId \"" + dirConfigSource.configId() + "\"\n" + //
+ "</config>";
+ }
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/DirConfigSource.java b/container-di/src/test/java/com/yahoo/container/di/DirConfigSource.java
new file mode 100644
index 00000000000..ec937a1a4ef
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/DirConfigSource.java
@@ -0,0 +1,69 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di;
+
+import com.yahoo.config.subscription.ConfigSource;
+import com.yahoo.config.subscription.ConfigSourceSet;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+/**
+ * @author Tony Vaagenes
+ * @author gjoranv
+ * @author ollivir
+ */
+public class DirConfigSource {
+ private final TemporaryFolder tempFolder = createTemporaryFolder();
+ public final ConfigSource configSource;
+
+ public DirConfigSource(String testSourcePrefix) {
+ this.configSource = new ConfigSourceSet(testSourcePrefix + new Random().nextLong());
+ }
+
+ public void writeConfig(String name, String contents) {
+ File file = new File(tempFolder.getRoot(), name + ".cfg");
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ printFile(file, contents + "\n");
+ }
+
+ public String configId() {
+ return "dir:" + tempFolder.getRoot().getPath();
+ }
+
+ public ConfigSource configSource() {
+ return configSource;
+ }
+
+ public void cleanup() {
+ tempFolder.delete();
+ }
+
+ private static void printFile(File f, String content) {
+ try (OutputStream out = new FileOutputStream(f)) {
+ out.write(content.getBytes("UTF-8"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static TemporaryFolder createTemporaryFolder() {
+ TemporaryFolder folder = new TemporaryFolder();
+ try {
+ folder.create();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return folder;
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java
new file mode 100644
index 00000000000..70dc4c8665c
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java
@@ -0,0 +1,750 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di.componentgraph.core;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import com.yahoo.collections.Pair;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.subscription.ConfigGetter;
+import com.yahoo.config.test.Test2Config;
+import com.yahoo.config.test.TestConfig;
+import com.yahoo.container.di.Osgi;
+import com.yahoo.container.di.componentgraph.Provider;
+import com.yahoo.container.di.config.JerseyBundlesConfig;
+import com.yahoo.container.di.config.JerseyInjectionConfig;
+import com.yahoo.container.di.config.RestApiContext;
+import com.yahoo.vespa.config.ConfigKey;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.function.Supplier;
+
+import static com.yahoo.container.di.componentgraph.core.ComponentGraph.isBindingAnnotation;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author gjoranv
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ComponentGraphTest {
+
+ public static class ConfigMap extends HashMap<ConfigKey<? extends ConfigInstance>, ConfigInstance> {
+ public ConfigMap() {
+ super();
+ }
+
+ public <T extends ConfigInstance> ConfigMap add(Class<T> clazz, String configId) {
+ ConfigKey<T> key = new ConfigKey<>(clazz, configId);
+ put(key, ConfigGetter.getConfig(key.getConfigClass(), key.getConfigId()));
+ return this;
+ }
+
+ public static <T extends ConfigInstance> ConfigMap newMap(Class<T> clazz, String configId) {
+ ConfigMap ret = new ConfigMap();
+ ret.add(clazz, configId);
+ return ret;
+ }
+ }
+
+ @Test
+ public void component_taking_config_can_be_instantiated() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ String configId = "raw:stringVal \"test-value\"";
+ Node componentNode = mockComponentNode(ComponentTakingConfig.class, configId);
+
+ componentGraph.add(componentNode);
+ componentGraph.complete();
+ componentGraph.setAvailableConfigs(ConfigMap.newMap(TestConfig.class, configId));
+
+ ComponentTakingConfig instance = componentGraph.getInstance(ComponentTakingConfig.class);
+ assertNotNull(instance);
+ assertThat(instance.config.stringVal(), is("test-value"));
+ }
+
+ @Test
+ public void all_created_components_are_returned_in_reverse_topological_order() {
+ for (int i = 0; i < 10; i++) {
+ Node innerComponent = mockComponentNode(SimpleComponent.class);
+ Node middleComponent = mockComponentNode(ComponentTakingComponent.class);
+ Node outerComponent = mockComponentNode(ComponentTakingComponentTakingComponent.class);
+
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(innerComponent);
+ componentGraph.add(middleComponent);
+ componentGraph.add(outerComponent);
+ componentGraph.complete();
+
+ innerComponent.constructInstance();
+ middleComponent.constructInstance();
+ outerComponent.constructInstance();
+
+ assertEquals(List.of(outerComponent.constructedInstance().get(), middleComponent.constructedInstance().get(), innerComponent.constructedInstance().get()),
+ componentGraph.allConstructedComponentsAndProviders());
+ }
+ }
+
+ @Test
+ public void component_can_be_injected_into_another_component() {
+ Node injectedComponent = mockComponentNode(SimpleComponent.class);
+ Node targetComponent = mockComponentNode(ComponentTakingComponent.class);
+ targetComponent.inject(injectedComponent);
+
+ Node destroyGlobalLookupComponent = mockComponentNode(SimpleComponent.class);
+
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(injectedComponent);
+ componentGraph.add(targetComponent);
+ componentGraph.add(destroyGlobalLookupComponent);
+ componentGraph.complete();
+
+ ComponentTakingComponent instance = componentGraph.getInstance(ComponentTakingComponent.class);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void interface_implementation_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(ComponentImpl.class));
+ componentGraph.add(mockComponentNode(ComponentTakingInterface.class));
+ componentGraph.complete();
+
+ ComponentTakingInterface instance = componentGraph.getInstance(ComponentTakingInterface.class);
+ assertTrue(instance.injected instanceof ComponentImpl);
+ }
+
+ @Test
+ public void private_class_with_public_ctor_can_be_instantiated() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(PrivateClassComponent.class));
+ componentGraph.complete();
+
+ PrivateClassComponent instance = componentGraph.getInstance(PrivateClassComponent.class);
+ assertNotNull(instance);
+ }
+
+ @Test
+ public void all_components_of_a_type_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleDerivedComponent.class));
+ componentGraph.add(mockComponentNode(ComponentTakingAllSimpleComponents.class));
+ componentGraph.complete();
+
+ ComponentTakingAllSimpleComponents instance = componentGraph.getInstance(ComponentTakingAllSimpleComponents.class);
+ assertThat(instance.simpleComponents.allComponents().size(), is(3));
+ }
+
+ @Test
+ public void empty_component_registry_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(ComponentTakingAllSimpleComponents.class));
+ componentGraph.complete();
+
+ ComponentTakingAllSimpleComponents instance = componentGraph.getInstance(ComponentTakingAllSimpleComponents.class);
+ assertThat(instance.simpleComponents.allComponents().size(), is(0));
+ }
+
+ @Test
+ public void component_registry_with_wildcard_upper_bound_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleDerivedComponent.class));
+ componentGraph.add(mockComponentNode(ComponentTakingAllSimpleComponentsUpperBound.class));
+ componentGraph.complete();
+
+ ComponentTakingAllSimpleComponentsUpperBound instance = componentGraph
+ .getInstance(ComponentTakingAllSimpleComponentsUpperBound.class);
+ assertThat(instance.simpleComponents.allComponents().size(), is(2));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void require_exception_when_injecting_registry_with_unknown_type_variable() {
+ @SuppressWarnings("rawtypes")
+ Class<ComponentTakingAllComponentsWithTypeVariable> clazz = ComponentTakingAllComponentsWithTypeVariable.class;
+
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleDerivedComponent.class));
+ componentGraph.add(mockComponentNode(clazz));
+ componentGraph.complete();
+
+ componentGraph.getInstance(clazz);
+ }
+
+ @Test
+ public void components_are_shared() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.complete();
+
+ SimpleComponent instance1 = componentGraph.getInstance(SimpleComponent.class);
+ SimpleComponent instance2 = componentGraph.getInstance(SimpleComponent.class);
+ assertThat(instance1, sameInstance(instance2));
+ }
+
+ @Test
+ public void singleton_components_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ String configId = "raw:stringVal \"test-value\"";
+
+ componentGraph.add(mockComponentNode(ComponentTakingComponent.class));
+ componentGraph.add(mockComponentNode(ComponentTakingConfig.class, configId));
+ componentGraph.add(mockComponentNode(SimpleComponent2.class));
+ componentGraph.complete();
+ componentGraph.setAvailableConfigs(ConfigMap.newMap(TestConfig.class, configId));
+
+ ComponentTakingComponent instance = componentGraph.getInstance(ComponentTakingComponent.class);
+ ComponentTakingConfig injected = (ComponentTakingConfig) instance.injectedComponent;
+ assertThat(injected.config.stringVal(), is("test-value"));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void require_error_when_multiple_components_match_a_singleton_dependency() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleDerivedComponent.class));
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(ComponentTakingComponent.class));
+ componentGraph.complete();
+ }
+
+ @Test
+ public void named_component_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleComponent.class, Names.named("named-test")));
+ componentGraph.add(mockComponentNode(ComponentTakingNamedComponent.class));
+ componentGraph.complete();
+ }
+
+ @Test
+ public void config_keys_can_be_retrieved() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(ComponentTakingConfig.class, "raw:stringVal \"component1\""));
+ componentGraph.add(mockComponentNode(ComponentTakingConfig.class, "raw:stringVal \"component2\""));
+ componentGraph.add(new ComponentRegistryNode(ComponentTakingConfig.class));
+ componentGraph.complete();
+
+ Set<ConfigKey<? extends ConfigInstance>> configKeys = componentGraph.configKeys();
+ assertThat(configKeys.size(), is(2));
+
+ configKeys.forEach(key -> {
+ assertThat(key.getConfigClass(), equalTo(TestConfig.class));
+ assertThat(key.getConfigId(), containsString("component"));
+ });
+ }
+
+ @Test
+ public void providers_can_be_instantiated() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(ExecutorProvider.class));
+ componentGraph.complete();
+
+ assertNotNull(componentGraph.getInstance(Executor.class));
+ }
+
+ @Test
+ public void providers_can_be_inherited() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(DerivedExecutorProvider.class));
+ componentGraph.complete();
+
+ assertNotNull(componentGraph.getInstance(Executor.class));
+ }
+
+ @Test
+ public void providers_can_deliver_a_new_instance_for_each_component() {
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNode(NewIntProvider.class));
+ componentGraph.complete();
+
+ Integer instance1 = componentGraph.getInstance(Integer.class);
+ Integer instance2 = componentGraph.getInstance(Integer.class);
+ assertEquals(1, instance1.intValue());
+ assertEquals(2, instance2.intValue());
+ }
+
+ @Test
+ public void providers_can_be_injected_explicitly() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ Node componentTakingExecutor = mockComponentNode(ComponentTakingExecutor.class);
+ Node executorProvider = mockComponentNode(ExecutorProvider.class);
+ componentTakingExecutor.inject(executorProvider);
+
+ componentGraph.add(executorProvider);
+ componentGraph.add(mockComponentNode(ExecutorProvider.class));
+
+ componentGraph.add(componentTakingExecutor);
+
+ componentGraph.complete();
+ assertNotNull(componentGraph.getInstance(ComponentTakingExecutor.class));
+ }
+
+ @Test
+ public void global_providers_can_be_injected() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ componentGraph.add(mockComponentNode(ComponentTakingExecutor.class));
+ componentGraph.add(mockComponentNode(ExecutorProvider.class));
+ componentGraph.add(mockComponentNode(FailOnGetIntProvider.class));
+ componentGraph.complete();
+
+ assertNotNull(componentGraph.getInstance(ComponentTakingExecutor.class));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void throw_if_multiple_global_providers_exist() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ componentGraph.add(mockComponentNode(ExecutorProvider.class));
+ componentGraph.add(mockComponentNode(ExecutorProvider.class));
+ componentGraph.add(mockComponentNode(ComponentTakingExecutor.class));
+ componentGraph.complete();
+ }
+
+ @Test
+ public void provider_is_not_used_when_component_of_provided_class_exists() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ componentGraph.add(mockComponentNode(SimpleComponent.class));
+ componentGraph.add(mockComponentNode(SimpleComponentProviderThatThrows.class));
+ componentGraph.add(mockComponentNode(ComponentTakingComponent.class));
+ componentGraph.complete();
+
+ SimpleComponent injectedComponent = componentGraph.getInstance(ComponentTakingComponent.class).injectedComponent;
+ assertNotNull(injectedComponent);
+ }
+
+ //TODO: move
+ @Test
+ public void check_if_annotation_is_a_binding_annotation() {
+ assertTrue(isBindingAnnotation(Names.named("name")));
+ assertFalse(isBindingAnnotation(Named.class.getAnnotations()[0]));
+ }
+
+ @Test
+ public void cycles_gives_exception() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ Node node1 = mockComponentNode(ComponentCausingCycle.class);
+ Node node2 = mockComponentNode(ComponentCausingCycle.class);
+
+ node1.inject(node2);
+ node2.inject(node1);
+
+ componentGraph.add(node1);
+ componentGraph.add(node2);
+
+ try {
+ componentGraph.complete();
+ fail("Cycle exception expected.");
+ } catch (Throwable e) {
+ assertThat(e.getMessage(), containsString("cycle"));
+ assertThat(e.getMessage(), containsString("ComponentCausingCycle"));
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void abstract_classes_are_rejected() {
+ new ComponentNode(ComponentId.fromString("Test"), "", AbstractClass.class);
+ }
+
+ @Test
+ public void inject_constructor_is_preferred() {
+ assertThatComponentCanBeCreated(ComponentWithInjectConstructor.class);
+ }
+
+ @Test
+ public void constructor_with_most_parameters_is_preferred() {
+ assertThatComponentCanBeCreated(ComponentWithMultipleConstructors.class);
+ }
+
+ public void assertThatComponentCanBeCreated(Class<?> clazz) {
+ ComponentGraph componentGraph = new ComponentGraph();
+ String configId = "raw:stringVal \"dummy\"";
+
+ componentGraph.add(mockComponentNode(clazz, configId));
+ componentGraph.complete();
+
+ componentGraph.setAvailableConfigs(ConfigMap.newMap(TestConfig.class, configId).add(Test2Config.class, configId));
+
+ assertNotNull(componentGraph.getInstance(clazz));
+ }
+
+ @Test
+ public void require_fallback_to_child_injector() {
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ componentGraph.add(mockComponentNode(ComponentTakingExecutor.class));
+
+ componentGraph.complete(singletonExecutorInjector);
+ assertNotNull(componentGraph.getInstance(ComponentTakingExecutor.class));
+ }
+
+ @Test
+ public void child_injector_can_inject_multiple_instances_for_same_key() {
+ Pair<Integer, Pair<Executor, Executor>> graph = buildGraphWithChildInjector(Executors::newSingleThreadExecutor);
+ int graphSize = graph.getFirst();
+ Executor executorA = graph.getSecond().getFirst();
+ Executor executorB = graph.getSecond().getSecond();
+
+ assertThat(graphSize, is(4));
+ assertThat(executorA, not(sameInstance(executorB)));
+ }
+
+ @Test
+ public void components_injected_via_child_injector_can_be_shared() {
+ Executor commonExecutor = Executors.newSingleThreadExecutor();
+ Pair<Integer, Pair<Executor, Executor>> graph = buildGraphWithChildInjector(() -> commonExecutor);
+ int graphSize = graph.getFirst();
+ Executor executorA = graph.getSecond().getFirst();
+ Executor executorB = graph.getSecond().getSecond();
+
+ assertThat(graphSize, is(3));
+ assertThat(executorA, sameInstance(executorB));
+ }
+
+ private Pair<Integer, Pair<Executor, Executor>> buildGraphWithChildInjector(Supplier<Executor> executorProvider) {
+ Injector childInjector = Guice.createInjector(new AbstractModule() {
+ @Override
+ public void configure() {
+ bind(Executor.class).toProvider(executorProvider::get);
+ }
+ });
+
+ ComponentGraph componentGraph = new ComponentGraph();
+
+ Key<ComponentTakingExecutor> keyA = Key.get(ComponentTakingExecutor.class, Names.named("A"));
+ Key<ComponentTakingExecutor> keyB = Key.get(ComponentTakingExecutor.class, Names.named("B"));
+
+ componentGraph.add(mockComponentNode(keyA));
+ componentGraph.add(mockComponentNode(keyB));
+
+ componentGraph.complete(childInjector);
+
+ return new Pair<>(componentGraph.size(),
+ new Pair<>(componentGraph.getInstance(keyA).executor, componentGraph.getInstance(keyB).executor));
+ }
+
+ @Test
+ public void providers_can_be_reused() {
+
+ ComponentGraph oldGraph = createReusingGraph();
+ Executor executor = oldGraph.getInstance(Executor.class);
+
+ ComponentGraph newGraph = createReusingGraph();
+ newGraph.reuseNodes(oldGraph);
+
+ Executor newExecutor = newGraph.getInstance(Executor.class);
+ assertThat(executor, sameInstance(newExecutor));
+ }
+
+ private ComponentGraph createReusingGraph() {
+ ComponentGraph graph = new ComponentGraph();
+ graph.add(mockComponentNodeWithId(ExecutorProvider.class, "dummyId"));
+ graph.complete();
+ graph.setAvailableConfigs(Collections.emptyMap());
+ return graph;
+ }
+
+ @Test
+ public void component_id_can_be_injected() {
+ String componentId = "myId:1.2@namespace";
+
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(mockComponentNodeWithId(ComponentTakingComponentId.class, componentId));
+ componentGraph.complete();
+
+ assertThat(componentGraph.getInstance(ComponentTakingComponentId.class).componentId, is(ComponentId.fromString(componentId)));
+ }
+
+ @Test
+ public void rest_api_context_can_be_instantiated() {
+ String configId = "raw:\"\"";
+
+ Class<RestApiContext> clazz = RestApiContext.class;
+ JerseyNode jerseyNode = new JerseyNode(uniqueComponentId(clazz.getName()), configId, clazz, new Osgi() {
+ });
+
+ ComponentGraph componentGraph = new ComponentGraph();
+ componentGraph.add(jerseyNode);
+ componentGraph.complete();
+
+ componentGraph
+ .setAvailableConfigs(ConfigMap.newMap(JerseyBundlesConfig.class, configId).add(JerseyInjectionConfig.class, configId));
+
+ RestApiContext restApiContext = componentGraph.getInstance(clazz);
+ assertNotNull(restApiContext);
+ assertThat(restApiContext.getBundles().size(), is(0));
+ }
+
+ //Note that all Components must be defined in a static context,
+ //otherwise their constructor will take the outer class as the first parameter.
+ private static int counter = 0;
+
+ private class PrivateClassComponent {
+ public PrivateClassComponent() { }
+ }
+
+ public static class SimpleComponent extends AbstractComponent {
+ }
+
+ public static class SimpleComponent2 extends AbstractComponent {
+ }
+
+ public static class SimpleDerivedComponent extends SimpleComponent {
+ }
+
+ public interface ComponentBase { }
+ public static class ComponentImpl implements ComponentBase { }
+ public static class ComponentTakingInterface {
+ ComponentBase injected;
+ public ComponentTakingInterface(ComponentBase componentBase) {
+ injected = componentBase;
+ }
+ }
+
+ public static class ComponentTakingConfig extends SimpleComponent {
+ private final TestConfig config;
+
+ public ComponentTakingConfig(TestConfig config) {
+ assertThat(config, notNullValue());
+ this.config = config;
+ }
+ }
+
+ public static class ComponentTakingComponent extends AbstractComponent {
+ private final SimpleComponent injectedComponent;
+
+ public ComponentTakingComponent(SimpleComponent injectedComponent) {
+ assertThat(injectedComponent, notNullValue());
+ this.injectedComponent = injectedComponent;
+ }
+ }
+
+ public static class ComponentTakingComponentTakingComponent extends AbstractComponent {
+ private final ComponentTakingComponent injectedComponent;
+
+ public ComponentTakingComponentTakingComponent(ComponentTakingComponent injectedComponent) {
+ assertThat(injectedComponent, notNullValue());
+ this.injectedComponent = injectedComponent;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ComponentTakingConfigAndComponent extends AbstractComponent {
+ private final TestConfig config;
+ private final SimpleComponent simpleComponent;
+
+ public ComponentTakingConfigAndComponent(TestConfig config, SimpleComponent injectedComponent) {
+ assertThat(config, notNullValue());
+ assertThat(injectedComponent, notNullValue());
+ this.config = config;
+ this.simpleComponent = injectedComponent;
+ }
+ }
+
+ public static class ComponentTakingAllSimpleComponents extends AbstractComponent {
+ public final ComponentRegistry<SimpleComponent> simpleComponents;
+
+ public ComponentTakingAllSimpleComponents(ComponentRegistry<SimpleComponent> simpleComponents) {
+ assertThat(simpleComponents, notNullValue());
+ this.simpleComponents = simpleComponents;
+ }
+ }
+
+ public static class ComponentTakingAllSimpleComponentsUpperBound extends AbstractComponent {
+ private final ComponentRegistry<? extends SimpleComponent> simpleComponents;
+
+ public ComponentTakingAllSimpleComponentsUpperBound(ComponentRegistry<? extends SimpleComponent> simpleComponents) {
+ assertThat(simpleComponents, notNullValue());
+ this.simpleComponents = simpleComponents;
+ }
+ }
+
+ public static class ComponentTakingAllComponentsWithTypeVariable<COMPONENT extends AbstractComponent> extends AbstractComponent {
+ public ComponentTakingAllComponentsWithTypeVariable(ComponentRegistry<COMPONENT> simpleComponents) {
+ assertThat(simpleComponents, notNullValue());
+ }
+ }
+
+ public static class ComponentTakingNamedComponent extends AbstractComponent {
+ public ComponentTakingNamedComponent(@Named("named-test") SimpleComponent injectedComponent) {
+ assertThat(injectedComponent, notNullValue());
+ }
+ }
+
+ public static class ComponentCausingCycle extends AbstractComponent {
+ public ComponentCausingCycle(ComponentCausingCycle component) {
+ }
+ }
+
+ public static class SimpleComponentProviderThatThrows implements Provider<SimpleComponent> {
+ public SimpleComponent get() {
+ throw new AssertionError("Should never be called.");
+ }
+
+ public void deconstruct() {
+ }
+ }
+
+ public static class ExecutorProvider implements Provider<Executor> {
+ private Executor executor = Executors.newSingleThreadExecutor();
+
+ public Executor get() {
+ return executor;
+ }
+
+ public void deconstruct() {
+ /*TODO */ }
+ }
+
+ public static class DerivedExecutorProvider extends ExecutorProvider {
+ }
+
+ public static class FailOnGetIntProvider implements Provider<Integer> {
+
+ public Integer get() {
+ fail("Should never be called.");
+ return null;
+ }
+
+ public void deconstruct() {
+ }
+
+ }
+
+ public static class NewIntProvider implements Provider<Integer> {
+ int i = 0;
+
+ public Integer get() {
+ return ++i;
+ }
+
+ public void deconstruct() {
+ }
+ }
+
+ public static class ComponentTakingExecutor extends AbstractComponent {
+ private final Executor executor;
+
+ public ComponentTakingExecutor(Executor executor) {
+ assertThat(executor, notNullValue());
+ this.executor = executor;
+ }
+ }
+
+ public static class ComponentWithInjectConstructor {
+
+ public ComponentWithInjectConstructor(TestConfig c, Test2Config c2) {
+ throw new RuntimeException("Should not be called");
+ }
+
+ @Inject
+ public ComponentWithInjectConstructor(Test2Config c) {
+ }
+
+ }
+
+ public static class ComponentWithMultipleConstructors {
+
+ private ComponentWithMultipleConstructors(int dummy) {
+ }
+
+ public ComponentWithMultipleConstructors() {
+ this(0);
+ throw new RuntimeException("Should not be called");
+ }
+
+ public ComponentWithMultipleConstructors(TestConfig c, Test2Config c2) {
+ this(0);
+ }
+
+ public ComponentWithMultipleConstructors(Test2Config c) {
+ this();
+ }
+
+ }
+
+ public static class ComponentTakingComponentId {
+ private final ComponentId componentId;
+
+ public ComponentTakingComponentId(ComponentId componentId) {
+ this.componentId = componentId;
+ }
+ }
+
+ public static ComponentId uniqueComponentId(String className) {
+ counter += 1;
+ return ComponentId.fromString(className + counter);
+ }
+
+ public static Node mockComponentNode(Key<?> key) {
+ return mockComponentNode(key.getTypeLiteral().getRawType(), "", key.getAnnotation());
+ }
+
+ public static Node mockComponentNode(Class<?> clazz, String configId, Annotation key) {
+ return new ComponentNode(uniqueComponentId(clazz.getName()), configId, clazz, key);
+ }
+
+ public static Node mockComponentNode(Class<?> clazz, String configId) {
+ return new ComponentNode(uniqueComponentId(clazz.getName()), configId, clazz, null);
+ }
+
+ public static Node mockComponentNode(Class<?> clazz, Annotation key) {
+ return new ComponentNode(uniqueComponentId(clazz.getName()), "", clazz, key);
+ }
+
+ public static Node mockComponentNode(Class<?> clazz) {
+ return new ComponentNode(uniqueComponentId(clazz.getName()), "", clazz, null);
+ }
+
+ public static Node mockComponentNodeWithId(Class<?> clazz, String componentId, String configId /*= ""*/, Annotation key /*= null*/) {
+ return new ComponentNode(ComponentId.fromString(componentId), configId, clazz, key);
+ }
+
+ public static Node mockComponentNodeWithId(Class<?> clazz, String componentId, String configId /*= ""*/) {
+ return new ComponentNode(ComponentId.fromString(componentId), configId, clazz, null);
+ }
+
+ public static Node mockComponentNodeWithId(Class<?> clazz, String componentId) {
+ return new ComponentNode(ComponentId.fromString(componentId), "", clazz, null);
+ }
+
+ public static Injector singletonExecutorInjector = Guice.createInjector(new AbstractModule() {
+ @Override
+ public void configure() {
+ bind(Executor.class).toInstance(Executors.newSingleThreadExecutor());
+ }
+ });
+
+ public static abstract class AbstractClass {
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/FallbackToGuiceInjectorTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/FallbackToGuiceInjectorTest.java
new file mode 100644
index 00000000000..7c517d67960
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/FallbackToGuiceInjectorTest.java
@@ -0,0 +1,151 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di.componentgraph.core;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.ComponentId;
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.container.di.componentgraph.core.ComponentGraph;
+import com.yahoo.container.di.componentgraph.core.ComponentNode;
+import com.yahoo.container.di.componentgraph.core.Node;
+import com.yahoo.vespa.config.ConfigKey;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author Tony Vaagenes
+ * @author gjoranv
+ */
+@SuppressWarnings("unused")
+public class FallbackToGuiceInjectorTest {
+
+ private ComponentGraph componentGraph;
+ private Injector injector;
+ private Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configs =
+ new HashMap<>();
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Before
+ public void createGraph() {
+ injector = Guice.createInjector();
+ componentGraph = new ComponentGraph(0);
+ }
+
+ public static class MyComponent extends AbstractComponent {
+ private final String url;
+ private final Executor executor;
+
+ @Inject
+ public MyComponent(@Named("url") String url, Executor executor) {
+ this.url = url;
+ this.executor = executor;
+ }
+
+ public MyComponent() {
+ throw new RuntimeException("Constructor annotated with @Inject is preferred.");
+ }
+ }
+
+ public static class ComponentTakingDefaultString{
+ private final String injectedString;
+
+ public ComponentTakingDefaultString(String empty_string_created_by_guice) {
+ this.injectedString = empty_string_created_by_guice;
+ }
+ }
+
+ public static class ComponentThatCannotBeConstructed {
+ public ComponentThatCannotBeConstructed(Integer cannot_be_injected_because_Integer_has_no_default_ctor) { }
+ }
+
+ @Test
+ public void guice_injector_is_used_when_no_global_component_exists() {
+ setInjector(
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Executor.class).toInstance(Executors.newSingleThreadExecutor());
+ bind(String.class).annotatedWith(Names.named("url")).toInstance("http://yahoo.com");
+ }
+ }));
+
+ register(MyComponent.class);
+ complete();
+
+ MyComponent component = getInstance(MyComponent.class);
+ assertThat(component.url, is("http://yahoo.com"));
+ assertNotNull(component.executor);
+ }
+
+ @Test
+ public void guice_injector_creates_a_new_instance_with_default_ctor_when_no_explicit_binding_exists() {
+ setInjector(emptyGuiceInjector());
+ register(ComponentTakingDefaultString.class);
+ complete();
+
+ ComponentTakingDefaultString component = getInstance(ComponentTakingDefaultString.class);
+ assertThat(component.injectedString, is(""));
+ }
+
+ @Test
+ public void guice_injector_fails_when_no_explicit_binding_exists_and_class_has_no_default_ctor() {
+ setInjector(emptyGuiceInjector());
+ register(ComponentThatCannotBeConstructed.class);
+
+ exception.expect(RuntimeException.class);
+ exception.expectMessage("When resolving dependencies of 'com.yahoo.container.di.componentgraph.core.FallbackToGuiceInjectorTest$ComponentThatCannotBeConstructed'");
+ complete();
+ }
+
+ public void register(Class<?> componentClass) {
+ componentGraph.add(mockComponentNode(componentClass));
+ }
+
+ public ComponentId toId(Class<?> componentClass) {
+ return ComponentId.fromString(componentClass.getName());
+ }
+
+ @SuppressWarnings("unchecked")
+ private Node mockComponentNode(Class<?> componentClass) {
+ return new ComponentNode(toId(componentClass), toId(componentClass).toString(), (Class<Object>)componentClass, null);
+ }
+
+ public <T> T getInstance(Class<T> componentClass) {
+ return componentGraph.getInstance(componentClass);
+ }
+
+ public void complete() {
+ componentGraph.complete(injector);
+ componentGraph.setAvailableConfigs(configs);
+ }
+
+ public void setInjector(Injector injector) {
+ this.injector = injector;
+ }
+
+ private Injector emptyGuiceInjector() {
+ return Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ }
+ });
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java
new file mode 100644
index 00000000000..f30f9260830
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java
@@ -0,0 +1,68 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di.componentgraph.core;
+
+import com.yahoo.container.bundle.MockBundle;
+import com.yahoo.container.di.config.RestApiContext;
+import com.yahoo.container.di.osgi.OsgiUtil;
+import org.junit.Test;
+import org.osgi.framework.wiring.BundleWiring;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ * @author ollivir
+ */
+
+public class JerseyNodeTest {
+ private MockBundle bundle;
+ private List<String> bundleClasses;
+ private final Map<String, String> resources;
+
+ public JerseyNodeTest() {
+ resources = new HashMap<>();
+ resources.put("com/foo", "com/foo/Foo.class");
+ resources.put("com/bar", "com/bar/Bar.class");
+ bundle = new MockBundle() {
+ @Override
+ public Collection<String> listResources(String path, String ignored, int options) {
+ if ((options & BundleWiring.LISTRESOURCES_RECURSE) != 0 && path.equals("/")) {
+ return resources.values();
+ } else {
+ return Collections.singleton(resources.get(path));
+ }
+ }
+ };
+ bundleClasses = new ArrayList<>(resources.values());
+ }
+
+ @Test
+ public void all_bundle_entries_are_returned_when_no_packages_are_given() {
+ Collection<String> entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Collections.emptySet());
+ assertThat(entries, containsInAnyOrder(bundleClasses.toArray()));
+ }
+
+ @Test
+ public void only_bundle_entries_from_the_given_packages_are_returned() {
+ Collection<String> entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Collections.singleton("com.foo"));
+ assertThat(entries, contains(resources.get("com/foo")));
+ }
+
+ @Test
+ public void bundle_info_is_initialized() {
+ RestApiContext.BundleInfo bundleInfo = JerseyNode.createBundleInfo(bundle, Collections.emptyList());
+ assertThat(bundleInfo.symbolicName, is(bundle.getSymbolicName()));
+ assertThat(bundleInfo.version, is(bundle.getVersion()));
+ assertThat(bundleInfo.fileLocation, is(bundle.getLocation()));
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.java
new file mode 100644
index 00000000000..e61e90cd718
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.java
@@ -0,0 +1,254 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di.componentgraph.core;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.config.subscription.ConfigGetter;
+import com.yahoo.config.test.TestConfig;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ComponentTakingAllSimpleComponents;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ComponentTakingComponent;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ComponentTakingConfig;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ComponentTakingConfigAndComponent;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ComponentTakingExecutor;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.ExecutorProvider;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent;
+import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent2;
+import com.yahoo.vespa.config.ConfigKey;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ReuseComponentsTest {
+ @Test
+ public void require_that_component_is_reused_when_componentNode_is_unmodified() {
+ reuseAndTest(SimpleComponent.class, SimpleComponent.class);
+ reuseAndTest(ExecutorProvider.class, Executor.class);
+ }
+
+ private <T> void reuseAndTest(Class<?> classToRegister, Class<T> classToLookup) {
+ ComponentGraph graph = buildGraphAndSetNoConfigs(classToRegister);
+ T instance = getComponent(graph, classToLookup);
+
+ ComponentGraph newGraph = buildGraphAndSetNoConfigs(classToRegister);
+ newGraph.reuseNodes(graph);
+ T instance2 = getComponent(newGraph, classToLookup);
+
+ assertThat(instance2, sameInstance(instance));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void require_that_component_is_not_reused_when_class_is_changed() {
+ ComponentGraph graph = buildGraphAndSetNoConfigs(SimpleComponent.class);
+ SimpleComponent instance = getComponent(graph, SimpleComponent.class);
+
+ ComponentGraph newGraph = buildGraphAndSetNoConfigs(SimpleComponent2.class);
+ newGraph.reuseNodes(graph);
+ SimpleComponent2 instance2 = getComponent(newGraph, SimpleComponent2.class);
+
+ assertThat(instance2.getId(), is(instance.getId()));
+ @SuppressWarnings("unused")
+ SimpleComponent throwsException = getComponent(newGraph, SimpleComponent.class);
+ }
+
+ @Test
+ public void require_that_component_is_not_reused_when_config_is_changed() {
+ Class<ComponentTakingConfig> componentClass = ComponentTakingConfig.class;
+
+ ComponentGraph graph = buildGraph(componentClass);
+ graph.setAvailableConfigs(Collections.singletonMap(new ConfigKey<>(TestConfig.class, "component"),
+ ConfigGetter.getConfig(TestConfig.class, "raw: stringVal \"oldConfig\"")));
+ ComponentTakingConfig instance = getComponent(graph, componentClass);
+
+ ComponentGraph newGraph = buildGraph(componentClass);
+ newGraph.setAvailableConfigs(Collections.singletonMap(new ConfigKey<>(TestConfig.class, "component"),
+ ConfigGetter.getConfig(TestConfig.class, "raw: stringVal \"newConfig\"")));
+ newGraph.reuseNodes(graph);
+ ComponentTakingConfig instance2 = getComponent(newGraph, componentClass);
+
+ assertThat(instance2, not(sameInstance(instance)));
+ }
+
+ @Test
+ public void require_that_component_is_not_reused_when_injected_component_is_changed() {
+ Function<String, ComponentGraph> buildGraph = config -> {
+ ComponentGraph graph = new ComponentGraph();
+
+ ComponentNode rootComponent = mockComponentNode(ComponentTakingComponent.class, "root_component");
+
+ String configId = "componentTakingConfigId";
+ ComponentNode injectedComponent = mockComponentNode(ComponentTakingConfig.class, "injected_component", configId);
+
+ rootComponent.inject(injectedComponent);
+
+ graph.add(rootComponent);
+ graph.add(injectedComponent);
+
+ graph.complete();
+ graph.setAvailableConfigs(Collections.singletonMap(new ConfigKey<>(TestConfig.class, configId),
+ ConfigGetter.getConfig(TestConfig.class, "raw: stringVal \"" + config + "\"")));
+
+ return graph;
+ };
+
+ ComponentGraph oldGraph = buildGraph.apply("oldGraph");
+ ComponentTakingComponent oldInstance = getComponent(oldGraph, ComponentTakingComponent.class);
+
+ ComponentGraph newGraph = buildGraph.apply("newGraph");
+ newGraph.reuseNodes(oldGraph);
+ ComponentTakingComponent newInstance = getComponent(newGraph, ComponentTakingComponent.class);
+
+ assertThat(newInstance, not(sameInstance(oldInstance)));
+ }
+
+ @Test
+ public void require_that_component_is_not_reused_when_injected_component_registry_has_one_component_removed() {
+ Function<Boolean, ComponentGraph> buildGraph = useBothInjectedComponents -> {
+ ComponentGraph graph = new ComponentGraph();
+ graph.add(mockComponentNode(ComponentTakingAllSimpleComponents.class, "root_component"));
+
+ /* Below if-else has code duplication, but explicit ordering of the two components
+ * was necessary to reproduce erroneous behaviour in ComponentGraph.reuseNodes that
+ * occurred before ComponentRegistryNode got its own 'equals' implementation.
+ */
+ if (useBothInjectedComponents) {
+ graph.add(mockComponentNode(SimpleComponent.class, "injected_component2"));
+ graph.add(mockComponentNode(SimpleComponent.class, "injected_component1"));
+ } else {
+ graph.add(mockComponentNode(SimpleComponent.class, "injected_component1"));
+ }
+
+ graph.complete();
+ graph.setAvailableConfigs(Collections.emptyMap());
+ return graph;
+ };
+
+ ComponentGraph oldGraph = buildGraph.apply(true);
+ ComponentRegistry<SimpleComponent> oldSimpleComponentRegistry = getComponent(oldGraph, ComponentTakingAllSimpleComponents.class).simpleComponents;
+
+ ComponentGraph newGraph = buildGraph.apply(false);
+ newGraph.reuseNodes(oldGraph);
+ ComponentRegistry<SimpleComponent> newSimpleComponentRegistry = getComponent(newGraph, ComponentTakingAllSimpleComponents.class).simpleComponents;
+
+ assertThat(newSimpleComponentRegistry, not(sameInstance(oldSimpleComponentRegistry)));
+ }
+
+ @Test
+ public void require_that_injected_component_is_reused_even_when_dependent_component_is_changed() {
+ Function<String, ComponentGraph> buildGraph = config -> {
+ ComponentGraph graph = new ComponentGraph();
+
+ String configId = "componentTakingConfigAndComponent";
+ ComponentNode rootComponent = mockComponentNode(ComponentTakingConfigAndComponent.class, "root_component", configId);
+
+ ComponentNode injectedComponent = mockComponentNode(SimpleComponent.class, "injected_component");
+
+ rootComponent.inject(injectedComponent);
+
+ graph.add(rootComponent);
+ graph.add(injectedComponent);
+
+ graph.complete();
+ graph.setAvailableConfigs(Collections.singletonMap(new ConfigKey<>(TestConfig.class, configId),
+ ConfigGetter.getConfig(TestConfig.class, "raw: stringVal \"" + config + "\"")));
+
+ return graph;
+ };
+
+ ComponentGraph oldGraph = buildGraph.apply("oldGraph");
+ SimpleComponent oldInjectedComponent = getComponent(oldGraph, SimpleComponent.class);
+ ComponentTakingConfigAndComponent oldDependentComponent = getComponent(oldGraph, ComponentTakingConfigAndComponent.class);
+
+ ComponentGraph newGraph = buildGraph.apply("newGraph");
+ newGraph.reuseNodes(oldGraph);
+ SimpleComponent newInjectedComponent = getComponent(newGraph, SimpleComponent.class);
+ ComponentTakingConfigAndComponent newDependentComponent = getComponent(newGraph, ComponentTakingConfigAndComponent.class);
+
+ assertThat(newDependentComponent, not(sameInstance(oldDependentComponent)));
+ assertThat(newInjectedComponent, sameInstance(oldInjectedComponent));
+ }
+
+ @Test
+ public void require_that_node_depending_on_guice_node_is_reused() {
+ Supplier<ComponentGraph> makeGraph = () -> {
+ ComponentGraph graph = new ComponentGraph();
+ graph.add(mockComponentNode(ComponentTakingExecutor.class, "dummyId"));
+ graph.complete(ComponentGraphTest.singletonExecutorInjector);
+ graph.setAvailableConfigs(Collections.emptyMap());
+ return graph;
+ };
+
+ Function<ComponentGraph, ComponentTakingExecutor> componentRetriever = graph -> getComponent(graph, ComponentTakingExecutor.class);
+
+ ComponentGraph oldGraph = makeGraph.get();
+ componentRetriever.apply(oldGraph); // Ensure creation of GuiceNode
+ ComponentGraph newGraph = makeGraph.get();
+ newGraph.reuseNodes(oldGraph);
+ assertThat(componentRetriever.apply(oldGraph), sameInstance(componentRetriever.apply(newGraph)));
+ }
+
+ @Test
+ public void require_that_node_equals_only_checks_first_level_components_to_inject() {
+ Function<String, Node> createNodeWithInjectedNodeWithInjectedNode = indirectlyInjectedComponentId -> {
+ ComponentNode targetComponent = mockComponentNode(SimpleComponent.class, "target");
+ ComponentNode directlyInjectedComponent = mockComponentNode(SimpleComponent.class, "directlyInjected");
+ ComponentNode indirectlyInjectedComponent = mockComponentNode(SimpleComponent.class, indirectlyInjectedComponentId);
+ directlyInjectedComponent.inject(indirectlyInjectedComponent);
+ targetComponent.inject(directlyInjectedComponent);
+
+ completeNode(targetComponent);
+ completeNode(directlyInjectedComponent);
+ completeNode(indirectlyInjectedComponent);
+
+ return targetComponent;
+ };
+
+ Node targetNode1 = createNodeWithInjectedNodeWithInjectedNode.apply("indirectlyInjected_1");
+ Node targetNode2 = createNodeWithInjectedNodeWithInjectedNode.apply("indirectlyInjected_2");
+ assertThat(targetNode1, equalTo(targetNode2));
+ }
+
+ private void completeNode(ComponentNode node) {
+ node.setArguments(new Object[0]);
+ node.setAvailableConfigs(Collections.emptyMap());
+ }
+
+ private ComponentGraph buildGraph(Class<?> componentClass) {
+ String commonComponentId = "component";
+ ComponentGraph g = new ComponentGraph();
+ g.add(mockComponentNode(componentClass, commonComponentId, commonComponentId));
+ g.complete();
+ return g;
+ }
+
+ private ComponentGraph buildGraphAndSetNoConfigs(Class<?> componentClass) {
+ ComponentGraph g = buildGraph(componentClass);
+ g.setAvailableConfigs(Collections.emptyMap());
+ return g;
+ }
+
+ private static ComponentNode mockComponentNode(Class<?> clazz, String componentId, String configId) {
+ return new ComponentNode(new ComponentId(componentId), configId, clazz);
+ }
+
+ private static ComponentNode mockComponentNode(Class<?> clazz, String componentId) {
+ return mockComponentNode(clazz, componentId, "");
+ }
+
+ private static <T> T getComponent(ComponentGraph graph, Class<T> clazz) {
+ return graph.getInstance(clazz);
+ }
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java
new file mode 100644
index 00000000000..174ca301c59
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/CycleFinderTest.java
@@ -0,0 +1,85 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.container.di.componentgraph.cycle;
+
+import org.junit.Test;
+
+import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.A;
+import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.B;
+import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.C;
+import static com.yahoo.container.di.componentgraph.cycle.CycleFinderTest.Vertices.D;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ */
+public class CycleFinderTest {
+
+ enum Vertices {A, B, C, D}
+
+ @Test
+ public void graph_without_cycles_returns_no_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+ graph.edge(B, C);
+ graph.edge(A, C);
+ graph.edge(D, A);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), empty());
+ }
+
+ @Test
+ public void graph_with_cycle_returns_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+ graph.edge(B, C);
+ graph.edge(C, A);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), contains(A, B, C, A));
+ }
+
+ @Test
+ public void graph_with_self_referencing_vertex_returns_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, A);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), contains(A, A));
+ }
+
+ @Test
+ public void leading_nodes_are_stripped_from_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+ graph.edge(B, C);
+ graph.edge(C, B);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), contains(B, C, B));
+ }
+
+ @Test
+ public void findCycle_is_idempotent_with_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, A);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), contains(A, A));
+ assertThat(cycleFinder.findCycle(), contains(A, A));
+ }
+
+ @Test
+ public void findCycle_is_idempotent_without_cycle() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+
+ var cycleFinder = new CycleFinder<>(graph);
+ assertThat(cycleFinder.findCycle(), empty());
+ assertThat(cycleFinder.findCycle(), empty());
+ }
+
+}
diff --git a/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java
new file mode 100644
index 00000000000..069f72ad8e7
--- /dev/null
+++ b/container-di/src/test/java/com/yahoo/container/di/componentgraph/cycle/GraphTest.java
@@ -0,0 +1,67 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.container.di.componentgraph.cycle;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.A;
+import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.B;
+import static com.yahoo.container.di.componentgraph.cycle.GraphTest.Vertices.C;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ */
+public class GraphTest {
+
+ enum Vertices {A, B, C}
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void vertices_and_edges_are_added_and_can_be_retrieved() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+ graph.edge(B, C);
+ graph.edge(A, C);
+
+ assertThat(graph.getVertices().size(), is(3));
+ assertThat(graph.getAdjacent(A), containsInAnyOrder(B, C));
+ assertThat(graph.getAdjacent(B), containsInAnyOrder(C));
+ assertThat(graph.getAdjacent(C), empty());
+ }
+
+ @Test
+ public void null_vertices_are_not_allowed() {
+ var graph = new Graph<Vertices>();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Null vertices are not allowed");
+ graph.edge(A, null);
+ }
+
+ @Test
+ public void duplicate_edges_are_ignored() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, B);
+ graph.edge(A, B);
+
+ assertThat(graph.getAdjacent(A).size(), is(1));
+ }
+
+ @Test
+ public void self_edges_are_allowed() {
+ var graph = new Graph<Vertices>();
+ graph.edge(A, A);
+
+ assertThat(graph.getAdjacent(A), contains(A));
+ }
+
+}
diff --git a/container-di/src/test/vespa-configdef/config.di.int.def b/container-di/src/test/vespa-configdef/config.di.int.def
new file mode 100644
index 00000000000..a34539c4a0f
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.di.int.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+namespace=config.di
+
+intVal int default=1
diff --git a/container-di/src/test/vespa-configdef/config.di.string.def b/container-di/src/test/vespa-configdef/config.di.string.def
new file mode 100644
index 00000000000..396afe54f3f
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.di.string.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+namespace=config.di
+
+stringVal string default="_default_"
diff --git a/container-di/src/test/vespa-configdef/config.test.bootstrap1.def b/container-di/src/test/vespa-configdef/config.test.bootstrap1.def
new file mode 100644
index 00000000000..bdee16d99ea
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.bootstrap1.def
@@ -0,0 +1,4 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config.test
+
+dummy string default=""
diff --git a/container-di/src/test/vespa-configdef/config.test.bootstrap2.def b/container-di/src/test/vespa-configdef/config.test.bootstrap2.def
new file mode 100644
index 00000000000..b4fbffd8ae6
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.bootstrap2.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config.test
+
+dummy string default=""
+
diff --git a/container-di/src/test/vespa-configdef/config.test.components1.def b/container-di/src/test/vespa-configdef/config.test.components1.def
new file mode 100644
index 00000000000..bdee16d99ea
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.components1.def
@@ -0,0 +1,4 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=config.test
+
+dummy string default=""
diff --git a/container-di/src/test/vespa-configdef/config.test.test.def b/container-di/src/test/vespa-configdef/config.test.test.def
new file mode 100644
index 00000000000..d3e0ed17748
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.test.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+namespace=config.test
+
+stringVal string default="default"
diff --git a/container-di/src/test/vespa-configdef/config.test.test2.def b/container-di/src/test/vespa-configdef/config.test.test2.def
new file mode 100644
index 00000000000..d3e0ed17748
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.test2.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+namespace=config.test
+
+stringVal string default="default"
diff --git a/container-di/src/test/vespa-configdef/config.test.thread-pool.def b/container-di/src/test/vespa-configdef/config.test.thread-pool.def
new file mode 100644
index 00000000000..9e6b6694e84
--- /dev/null
+++ b/container-di/src/test/vespa-configdef/config.test.thread-pool.def
@@ -0,0 +1,5 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+namespace=config.test
+
+numThreads int