diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-di/src/test |
Publish
Diffstat (limited to 'container-di/src/test')
23 files changed, 1962 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..4b0c8fca4d1 --- /dev/null +++ b/container-di/src/test/java/com/yahoo/component/ComponentSpecTestCase.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.component; + +import junit.framework.TestCase; + +/** + * @author <a href="mailto:arnebef@yahoo-inc.com">Arne Bergene Fossaa</a> + */ +public class ComponentSpecTestCase extends TestCase { + + 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)); + + } + + 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)); + } + + 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..b14d56dd31d --- /dev/null +++ b/container-di/src/test/java/com/yahoo/component/provider/test/ComponentRegistryTestCase.java @@ -0,0 +1,93 @@ +// Copyright 2016 Yahoo Inc. 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 tonytv + */ +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..7359cc8ba66 --- /dev/null +++ b/container-di/src/test/java/com/yahoo/component/test/ComponentIdTestCase.java @@ -0,0 +1,39 @@ +// Copyright 2016 Yahoo Inc. 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 junit.framework.TestCase; + +/** + * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a> + */ +public class ComponentIdTestCase extends TestCase { + + 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()); + } + + 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/demo/Base.java b/container-di/src/test/java/demo/Base.java new file mode 100644 index 00000000000..7bc2c7b4404 --- /dev/null +++ b/container-di/src/test/java/demo/Base.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 demo; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.yahoo.component.ComponentId; +import com.yahoo.config.ConfigInstance; +import com.yahoo.container.di.ContainerTest; +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 java.util.HashMap; +import java.util.Map; + +/** + * @author tonytv + * @author gjoranv + */ +public class Base { + private ComponentGraph componentGraph; + private Injector injector; + private Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configs = + new HashMap<>(); + + @Before + public void createGraph() { + injector = Guice.createInjector(); + componentGraph = new ComponentGraph(0); + } + + 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); + } + + @SuppressWarnings("unchecked") + public void complete() { + componentGraph.complete(injector); + componentGraph.setAvailableConfigs(ContainerTest.convertMap(configs)); + } + + public void setInjector(Injector injector) { + this.injector = injector; + } + + @SuppressWarnings("unchecked") + public void addConfig(ConfigInstance configInstance, ComponentId id) { + configs.put(new ConfigKey<>((Class<ConfigInstance>)configInstance.getClass(), id.toString()), + configInstance); + } +} diff --git a/container-di/src/test/java/demo/ComponentConfigTest.java b/container-di/src/test/java/demo/ComponentConfigTest.java new file mode 100644 index 00000000000..5b3be2f549a --- /dev/null +++ b/container-di/src/test/java/demo/ComponentConfigTest.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 demo; + +import com.yahoo.config.test.ThreadPoolConfig; +import com.yahoo.container.di.componentgraph.Provider; +import org.junit.Test; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertNotNull; + + +/** + * @author tonytv + * @author gjoranv + */ +public class ComponentConfigTest extends Base { + public static class ThreadPoolExecutorProvider implements Provider<Executor> { + private ExecutorService executor; + + public ThreadPoolExecutorProvider(ThreadPoolConfig config) { + executor = Executors.newFixedThreadPool(config.numThreads()); + } + + @Override + public Executor get() { + return executor; + } + + @Override + public void deconstruct() { + executor.shutdown(); + } + } + + @Test + public void require_that_non_components_can_be_configured() { + register(ThreadPoolExecutorProvider.class); + addConfig(new ThreadPoolConfig(new ThreadPoolConfig.Builder().numThreads(4)), + toId(ThreadPoolExecutorProvider.class)); + complete(); + + Executor executor = getInstance(Executor.class); + assertNotNull(executor); + } +} diff --git a/container-di/src/test/java/demo/ComponentRegistryTest.java b/container-di/src/test/java/demo/ComponentRegistryTest.java new file mode 100644 index 00000000000..193f65048ab --- /dev/null +++ b/container-di/src/test/java/demo/ComponentRegistryTest.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 demo; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.provider.ComponentRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + + +/** + * @author tonytv + * @author gjoranv + */ +public class ComponentRegistryTest extends Base { + public static class SearchHandler extends AbstractComponent { + private final ComponentRegistry<Searcher> searchers; + + public SearchHandler(ComponentRegistry<Searcher> searchers) { + this.searchers = searchers; + } + } + + public static class Searcher extends AbstractComponent {} + + public static class FooSearcher extends Searcher {} + public static class BarSearcher extends Searcher {} + + @Test + public void require_that_component_registry_can_be_injected() { + register(SearchHandler.class); + register(FooSearcher.class); + register(BarSearcher.class); + complete(); + + SearchHandler handler = getInstance(SearchHandler.class); + + ComponentRegistry<Searcher> searchers = handler.searchers; + assertNotNull(searchers.getComponent(toId(FooSearcher.class))); + assertNotNull(searchers.getComponent(toId(BarSearcher.class))); + } +} diff --git a/container-di/src/test/java/demo/ContainerTestBase.java b/container-di/src/test/java/demo/ContainerTestBase.java new file mode 100644 index 00000000000..fdc73913052 --- /dev/null +++ b/container-di/src/test/java/demo/ContainerTestBase.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 demo; + +import com.google.inject.Guice; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.config.FileReference; +import com.yahoo.container.di.CloudSubscriberFactory; +import com.yahoo.container.di.Container; +import com.yahoo.container.di.ContainerTest; +import com.yahoo.container.di.Osgi; +import com.yahoo.container.di.componentgraph.core.ComponentGraph; +import org.junit.Before; +import org.osgi.framework.Bundle; +import scala.collection.*; +import scala.collection.immutable.*; +import scala.collection.immutable.Set; + +import java.util.Collection; +import java.util.List; + +/** + * @author tonytv + * @author gjoranv + */ +public class ContainerTestBase extends ContainerTest { + private ComponentGraph componentGraph; + + @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 void useBundles(Collection<FileReference> bundles) {} + + @Override + public Bundle getBundle(ComponentSpecification spec) { + throw new UnsupportedOperationException("getBundle not supported."); + } + }); + componentGraph = container.runOnce(componentGraph, Guice.createInjector()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public <T> T getInstance(Class<T> componentClass) { + return componentGraph.getInstance(componentClass); + } +} diff --git a/container-di/src/test/java/demo/DeconstructTest.java b/container-di/src/test/java/demo/DeconstructTest.java new file mode 100644 index 00000000000..10e9844f6de --- /dev/null +++ b/container-di/src/test/java/demo/DeconstructTest.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 demo; + +import com.yahoo.container.di.ContainerTest; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author tonytv + * @author gjoranv + */ +public class DeconstructTest extends ContainerTestBase { + public static class DeconstructableComponent extends ContainerTest.DestructableComponent { + private boolean isDeconstructed = false; + + @Override + public void deconstruct() { + isDeconstructed = true; + } + } + + @Test + public void require_that_unused_components_are_deconstructed() { + writeBootstrapConfigs("d1", DeconstructableComponent.class); + complete(); + + DeconstructableComponent d1 = getInstance(DeconstructableComponent.class); + + writeBootstrapConfigs("d2", DeconstructableComponent.class); + complete(); + + assertTrue(d1.isDeconstructed); + } +} diff --git a/container-di/src/test/java/demo/FallbackToGuiceInjectorTest.java b/container-di/src/test/java/demo/FallbackToGuiceInjectorTest.java new file mode 100644 index 00000000000..3d5550ab0a3 --- /dev/null +++ b/container-di/src/test/java/demo/FallbackToGuiceInjectorTest.java @@ -0,0 +1,104 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package demo; + +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 org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +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 tonytv + * @author gjoranv + */ +@SuppressWarnings("unused") +public class FallbackToGuiceInjectorTest extends Base { + 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) { } + } + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @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 'demo.FallbackToGuiceInjectorTest$ComponentThatCannotBeConstructed'"); + complete(); + } + + private Injector emptyGuiceInjector() { + return Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + } + }); + } +} diff --git a/container-di/src/test/scala/com/yahoo/container/di/ConfigRetrieverTest.scala b/container-di/src/test/scala/com/yahoo/container/di/ConfigRetrieverTest.scala new file mode 100644 index 00000000000..3eff01eed55 --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/ConfigRetrieverTest.scala @@ -0,0 +1,88 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.di + +import org.junit.Assert._ +import org.hamcrest.CoreMatchers.{is, instanceOf => hamcrestInstanceOf} +import com.yahoo.vespa.config.ConfigKey +import com.yahoo.config.test.{TestConfig, Bootstrap2Config, Bootstrap1Config} +import com.yahoo.container.di.ConfigRetriever.{ComponentsConfigs, BootstrapConfigs} +import org.junit.{Ignore, After, Before, Test} +import scala.collection.JavaConversions._ +import scala.reflect.ClassTag +import org.hamcrest.Matcher + +/** + * + * @author gjoranv + * @author tonytv + */ +class ConfigRetrieverTest { + var dirConfigSource: DirConfigSource = null + + @Before def setup() { + dirConfigSource = new DirConfigSource("ConfigRetrieverTest-") + } + @After def cleanup() { dirConfigSource.cleanup() } + + @Test + def require_that_bootstrap_configs_come_first() { + writeConfigs() + val retriever = createConfigRetriever() + val bootstrapConfigs = retriever.getConfigs(Set(), 0) + + assertThat(bootstrapConfigs, instanceOf[BootstrapConfigs]) + } + + @Test + def require_that_components_comes_after_bootstrap() { + writeConfigs() + val retriever = createConfigRetriever() + val bootstrapConfigs = retriever.getConfigs(Set(), 0) + + val testConfigKey = new ConfigKey(classOf[TestConfig], dirConfigSource.configId) + val componentsConfigs = retriever.getConfigs(Set(testConfigKey), 0) + + componentsConfigs match { + case ComponentsConfigs(configs) => assertThat(configs.size, is(3)) + case _ => fail("ComponentsConfigs has unexpected type: " + componentsConfigs) + } + } + + @Test(expected = classOf[IllegalArgumentException]) + @Ignore + def require_exception_upon_modified_components_keys_without_bootstrap() { + writeConfigs() + val retriever = createConfigRetriever() + val testConfigKey = new ConfigKey(classOf[TestConfig], dirConfigSource.configId) + val bootstrapConfigs = retriever.getConfigs(Set(), 0) + val componentsConfigs = retriever.getConfigs(Set(testConfigKey), 0) + retriever.getConfigs(Set(testConfigKey, new ConfigKey(classOf[TestConfig],"")), 0) + } + + @Test + def require_that_empty_components_keys_after_bootstrap_returns_components_configs() { + writeConfigs() + val retriever = createConfigRetriever() + assertThat(retriever.getConfigs(Set(), 0), instanceOf[BootstrapConfigs]) + assertThat(retriever.getConfigs(Set(), 0), instanceOf[ComponentsConfigs]) + } + + def writeConfigs() { + writeConfig("bootstrap1", """dummy "ignored" """") + writeConfig("bootstrap2", """dummy "ignored" """") + writeConfig("test", """stringVal "ignored" """") + } + + def createConfigRetriever() = { + val configId = dirConfigSource.configId + val subscriber = new CloudSubscriberFactory(dirConfigSource.configSource) + new ConfigRetriever( + Set(new ConfigKey(classOf[Bootstrap1Config], configId), + new ConfigKey(classOf[Bootstrap2Config], configId)), + subscriber.getSubscriber(_)) + } + + def writeConfig = dirConfigSource.writeConfig _ + + def instanceOf[T: ClassTag] = hamcrestInstanceOf(implicitly[ClassTag[T]].runtimeClass): Matcher[AnyRef] +} diff --git a/container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala b/container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala new file mode 100644 index 00000000000..b64de80e39f --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala @@ -0,0 +1,350 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.di + +import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.{SimpleComponent2, SimpleComponent} +import com.yahoo.container.di.componentgraph.Provider +import com.yahoo.container.di.componentgraph.core.{Node, ComponentGraph} +import org.junit.{Test, Before, After} +import org.junit.Assert._ +import org.hamcrest.CoreMatchers._ +import com.yahoo.config.test.TestConfig +import com.yahoo.component.AbstractComponent +import ContainerTest._ +import scala.collection.JavaConversions +import com.yahoo.config.di.IntConfig +import scala.concurrent.{future, Await} +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.Try +import com.yahoo.container.di.config.RestApiContext +import com.yahoo.container.bundle.MockBundle + +/** + * @author tonytv + * @author gjoranv + */ +class ContainerTest { + var dirConfigSource: DirConfigSource = null + + @Before def setup() { + dirConfigSource = new DirConfigSource("ContainerTest-") + } + + @After def cleanup() { + dirConfigSource.cleanup() + } + + @Test + def components_can_be_created_from_config() { + writeBootstrapConfigs() + dirConfigSource.writeConfig("test", """stringVal "myString" """) + + val container = newContainer(dirConfigSource) + + val component = createComponentTakingConfig(container.runOnce()) + assertThat(component.config.stringVal(), is("myString")) + + container.shutdownConfigurer() + } + + @Test + def components_are_reconfigured_after_config_update_without_bootstrap_configs() { + writeBootstrapConfigs() + dirConfigSource.writeConfig("test", """stringVal "original" """) + + val container = newContainer(dirConfigSource) + + val componentGraph = container.runOnce() + val component = createComponentTakingConfig(componentGraph) + + assertThat(component.config.stringVal(), is("original")) + + // Reconfigure + dirConfigSource.writeConfig("test", """stringVal "reconfigured" """) + container.reloadConfig(2) + + val newComponentGraph = container.runOnce(componentGraph) + val component2 = createComponentTakingConfig(newComponentGraph) + assertThat(component2.config.stringVal(), is("reconfigured")) + + container.shutdownConfigurer() + } + + @Test + def graph_is_updated_after_bootstrap_update() { + dirConfigSource.writeConfig("test", """stringVal "original" """) + writeBootstrapConfigs("id1") + + val container = newContainer(dirConfigSource) + + val graph = container.runOnce() + val component = createComponentTakingConfig(graph) + assertThat(component.getId.toString, is("id1")) + + writeBootstrapConfigsWithMultipleComponents(Array( + ("id1", classOf[ComponentTakingConfig]), + ("id2", classOf[ComponentTakingConfig]))) + + container.reloadConfig(2) + val newGraph = container.runOnce(graph) + + assertThat(ComponentGraph.getNode(newGraph, "id1"), notNullValue(classOf[Node])); + assertThat(ComponentGraph.getNode(newGraph, "id2"), notNullValue(classOf[Node])); + + container.shutdownConfigurer() + } + + //@Test TODO + def deconstructor_is_given_guice_components() { + } + + @Test + def osgi_component_is_deconstructed_when_not_reused() { + writeBootstrapConfigs("id1", classOf[DestructableComponent]) + + val container = newContainer(dirConfigSource) + + val oldGraph = container.runOnce() + val componentToDestruct = oldGraph.getInstance(classOf[DestructableComponent]) + + writeBootstrapConfigs("id2", classOf[DestructableComponent]) + container.reloadConfig(2) + container.runOnce(oldGraph) + assertTrue(componentToDestruct.deconstructed) + } + + @Test + def previous_graph_is_retained_when_new_graph_throws_exception() { + val simpleComponentEntry = ComponentEntry("simpleComponent", classOf[SimpleComponent]) + + writeBootstrapConfigs(Array(simpleComponentEntry)) + val container = newContainer(dirConfigSource) + var currentGraph = container.runOnce() + + val simpleComponent = currentGraph.getInstance(classOf[SimpleComponent]) + + writeBootstrapConfigs("thrower", classOf[ComponentThrowingException]) + dirConfigSource.writeConfig("test", """stringVal "myString" """) + container.reloadConfig(2) + try { + currentGraph = container.runOnce(currentGraph) + fail("Expected exception") + } catch { + case e: Exception => e.printStackTrace() + } + + val componentTakingConfigEntry = ComponentEntry("componentTakingConfig", classOf[ComponentTakingConfig]) + writeBootstrapConfigs(Array(simpleComponentEntry, componentTakingConfigEntry)) + container.reloadConfig(3) + currentGraph = container.runOnce(currentGraph) + + assertSame(simpleComponent, currentGraph.getInstance(classOf[SimpleComponent])) + assertNotNull(currentGraph.getInstance(classOf[ComponentTakingConfig])) + } + + @Test + def runOnce_hangs_waiting_for_valid_config_after_invalid_config() { + dirConfigSource.writeConfig("test", """stringVal "original" """) + writeBootstrapConfigs("myId", classOf[ComponentTakingConfig]) + + val container = newContainer(dirConfigSource) + var currentGraph = container.runOnce() + + writeBootstrapConfigs("thrower", classOf[ComponentThrowingException]) + container.reloadConfig(2) + + try { + currentGraph = container.runOnce(currentGraph) + fail("expected exception") + } catch { + case e: Exception => + } + + val newGraph = future { + currentGraph = container.runOnce(currentGraph) + currentGraph + } + + Try { + Await.ready(newGraph, 1 second) + } foreach { x => fail("Expected waiting for new config.") } + + + writeBootstrapConfigs("myId2", classOf[ComponentTakingConfig]) + container.reloadConfig(3) + + assertNotNull(Await.result(newGraph, 5 minutes)) + } + + + @Test + def bundle_info_is_set_on_rest_api_context() { + val clazz = classOf[RestApiContext] + + writeBootstrapConfigs("restApiContext", clazz) + dirConfigSource.writeConfig("jersey-bundles", """bundles[0].spec "mock-entry-to-enforce-a-MockBundle" """) + dirConfigSource.writeConfig("jersey-injection", """inject[0]" """) + + val container = newContainer(dirConfigSource) + val componentGraph = container.runOnce() + + val restApiContext = componentGraph.getInstance(clazz) + assertNotNull(restApiContext) + + assertThat(restApiContext.getBundles.size, is(1)) + assertThat(restApiContext.getBundles.get(0).symbolicName, is(MockBundle.SymbolicName)) + assertThat(restApiContext.getBundles.get(0).version, is(MockBundle.BundleVersion)) + + container.shutdownConfigurer() + } + + @Test + def restApiContext_has_all_components_injected() { + new JerseyInjectionTest { + assertFalse(restApiContext.getInjectableComponents.isEmpty) + assertThat(restApiContext.getInjectableComponents.size(), is(2)) + + container.shutdownConfigurer() + } + } + + // TODO: reuse injectedComponent as a named component when we support that + trait JerseyInjectionTest { + val restApiClass = classOf[RestApiContext] + val injectedClass = classOf[SimpleComponent] + val injectedComponentId = "injectedComponent" + val anotherComponentClass = classOf[SimpleComponent2] + val anotherComponentId = "anotherComponent" + + val componentsConfig = + ComponentEntry(injectedComponentId, injectedClass).asConfig(0) + "\n" + + ComponentEntry(anotherComponentId, anotherComponentClass).asConfig(1) + "\n" + + ComponentEntry("restApiContext", restApiClass).asConfig(2) + "\n" + + s"components[2].inject[0].id $injectedComponentId\n" + + s"components[2].inject[1].id $anotherComponentId\n" + + val injectionConfig = s"""inject[1] + |inject[0].instance $injectedComponentId + |inject[0].forClass "${injectedClass.getName}" + """.stripMargin + + dirConfigSource.writeConfig("components", componentsConfig) + dirConfigSource.writeConfig("bundles", "") + dirConfigSource.writeConfig("jersey-bundles", """bundles[0].spec "mock-entry-to-enforce-a-MockBundle" """) + dirConfigSource.writeConfig("jersey-injection", injectionConfig) + + val container = newContainer(dirConfigSource) + val componentGraph = container.runOnce() + + val restApiContext = componentGraph.getInstance(restApiClass) + } + + case class ComponentEntry(componentId: String, classId: Class[_]) { + def asConfig(position: Int) = { + <config> + |components[{position}].id "{componentId}" + |components[{position}].classId "{classId.getName}" + |components[{position}].configId "{dirConfigSource.configId}" + </config>.text.stripMargin.trim + } + } + + def writeBootstrapConfigs(componentEntries: Array[ComponentEntry]) { + dirConfigSource.writeConfig("bundles", "") + dirConfigSource.writeConfig("components", """ + components[%s] + %s + """.format(componentEntries.length, + componentEntries.zipWithIndex.map{ case (entry, index) => entry.asConfig(index) }.mkString("\n"))) + } + + def writeBootstrapConfigs(componentId: String = classOf[ComponentTakingConfig].getName, + classId: Class[_] = classOf[ComponentTakingConfig]) { + + writeBootstrapConfigs(Array(ComponentEntry(componentId, classId))) + } + + def writeBootstrapConfigsWithMultipleComponents(idAndClass: Array[(String, Class[_])]) { + writeBootstrapConfigs(idAndClass.map{case(id, classId) => ComponentEntry(id, classId)}) + } + + + @Test + def providers_are_destructed() { + writeBootstrapConfigs("id1", classOf[DestructableProvider]) + + val deconstructor = new ComponentDeconstructor { + def deconstruct(component: AnyRef) { + component match { + case c : AbstractComponent => c.deconstruct() + case p : Provider[_] => p.deconstruct() + } + } + } + + val container = newContainer(dirConfigSource, deconstructor) + + val oldGraph = container.runOnce() + val destructableEntity = oldGraph.getInstance(classOf[DestructableEntity]) + + writeBootstrapConfigs("id2", classOf[DestructableProvider]) + container.reloadConfig(2) + container.runOnce(oldGraph) + + assertTrue(destructableEntity.deconstructed) + } +} + + +object ContainerTest { + class DestructableEntity { + var deconstructed = false + } + + class DestructableProvider extends Provider[DestructableEntity] { + val instance = new DestructableEntity + + def get() = instance + + def deconstruct() { + require(instance.deconstructed == false) + instance.deconstructed = true + } + } + + class ComponentTakingConfig(val config: TestConfig) extends AbstractComponent { + require(config != null) + } + + class ComponentThrowingException(config:IntConfig) extends AbstractComponent { + throw new RuntimeException("This component can never be created") + } + + class DestructableComponent extends AbstractComponent { + var deconstructed = false + override def deconstruct() { + deconstructed = true + } + } + + class TestDeconstructor extends ComponentDeconstructor { + def deconstruct(component: AnyRef) { + component match { + case vespaComponent: DestructableComponent => vespaComponent.deconstruct() + case _ => + } + } + } + + private def newContainer(dirConfigSource: DirConfigSource, + deconstructor: ComponentDeconstructor = new TestDeconstructor()): + Container = { + new Container(new CloudSubscriberFactory(dirConfigSource.configSource), dirConfigSource.configId, deconstructor) + } + + def createComponentTakingConfig(componentGraph: ComponentGraph): ComponentTakingConfig = { + componentGraph.getInstance(classOf[ComponentTakingConfig]) + } + + def convertMap[K, V](map: java.util.Map[K, V]) = JavaConversions.mapAsScalaMap(map).toMap +} diff --git a/container-di/src/test/scala/com/yahoo/container/di/DirConfigSource.scala b/container-di/src/test/scala/com/yahoo/container/di/DirConfigSource.scala new file mode 100644 index 00000000000..20e4fcb03f8 --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/DirConfigSource.scala @@ -0,0 +1,49 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.di + +import java.io.{FileOutputStream, OutputStream, File} +import DirConfigSource._ +import java.util.Random +import org.junit.rules.TemporaryFolder +import com.yahoo.config.subscription.{ConfigSource, ConfigSourceSet} + +// TODO: Make this a junit rule. Does not yet work. Look out for junit updates +// (@Rule def configSourceRule = dirConfigSource) + +/** + * @author tonytv + * @author gjoranv + */ +class DirConfigSource(val testSourcePrefix: String) { + private val tempFolder = createTemporaryFolder() + + val configSource : ConfigSource = new ConfigSourceSet(testSourcePrefix + new Random().nextLong) + + def writeConfig(name: String, contents: String) { + val file = new File(tempFolder.getRoot, name + ".cfg") + if (!file.exists()) + file.createNewFile() + + printFile(file, contents + "\n") + } + + def configId = "dir:" + tempFolder.getRoot.getPath + + def cleanup() { + tempFolder.delete() + } +} + +private object DirConfigSource { + def printFile(f: File, content: String) { + var out: OutputStream = new FileOutputStream(f) + out.write(content.getBytes("UTF-8")) + out.close() + } + + def createTemporaryFolder() = { + val folder = new TemporaryFolder + folder.create() + folder + } +}
\ No newline at end of file diff --git a/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.scala b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.scala new file mode 100644 index 00000000000..43733923235 --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.scala @@ -0,0 +1,538 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.di.componentgraph.core + +import org.junit.Test +import org.junit.Assert._ +import org.hamcrest.CoreMatchers.{is, sameInstance, equalTo, not, containsString} + +import com.yahoo.component.provider.ComponentRegistry +import com.google.inject.name.{Names, Named} +import com.yahoo.component.{ComponentId, AbstractComponent} +import org.hamcrest.Matcher +import com.yahoo.vespa.config.ConfigKey +import com.yahoo.config.{ConfigInstance} +import com.yahoo.config.test.{TestConfig, Test2Config} +import java.util.concurrent.{Executors, Executor} +import com.google.inject.{Guice, Key, AbstractModule, Inject, Provider=>GuiceProvider} +import com.yahoo.container.di._ +import com.yahoo.config.subscription.ConfigGetter +import com.yahoo.container.di.config.{JerseyInjectionConfig, JerseyBundlesConfig, RestApiContext} +import com.yahoo.container.di.componentgraph.Provider + +/** + * @author gjoranv + * @author tonytv + */ +class ComponentGraphTest { + import ComponentGraphTest._ + + private def keyAndConfig[T <: ConfigInstance](clazz: Class[T], configId: String): (ConfigKey[T], T) = { + val key = new ConfigKey(clazz, configId) + key -> ConfigGetter.getConfig(key.getConfigClass, key.getConfigId.toString) + } + + @Test + def component_taking_config_can_be_instantiated() { + val componentGraph = new ComponentGraph + val configId = "raw:stringVal \"test-value\"" + val componentNode = mockComponentNode(classOf[ComponentTakingConfig], configId) + + componentGraph.add(componentNode) + componentGraph.complete() + componentGraph.setAvailableConfigs(Map(keyAndConfig(classOf[TestConfig], configId))) + + val instance = componentGraph.getInstance(classOf[ComponentTakingConfig]) + assertNotNull(instance) + assertThat(instance.config.stringVal(), is("test-value")) + } + + @Test + def component_can_be_injected_into_another_component() { + val injectedComponent = mockComponentNode(classOf[SimpleComponent]) + val targetComponent = mockComponentNode(classOf[ComponentTakingComponent]) + targetComponent.inject(injectedComponent) + + val destroyGlobalLookupComponent = mockComponentNode(classOf[SimpleComponent]) + + val componentGraph = new ComponentGraph + componentGraph.add(injectedComponent) + componentGraph.add(targetComponent) + componentGraph.add(destroyGlobalLookupComponent) + componentGraph.complete() + + + val instance = componentGraph.getInstance(classOf[ComponentTakingComponent]) + assertNotNull(instance) + } + + @Test + def all_components_of_a_type_can_be_injected() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleDerivedComponent])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingAllSimpleComponents])) + componentGraph.complete() + + val instance = componentGraph.getInstance(classOf[ComponentTakingAllSimpleComponents]) + assertThat(instance.simpleComponents.allComponents().size(), is(3)) + } + + @Test + def empty_component_registry_can_be_injected() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[ComponentTakingAllSimpleComponents])) + componentGraph.complete() + + val instance = componentGraph.getInstance(classOf[ComponentTakingAllSimpleComponents]) + assertThat(instance.simpleComponents.allComponents().size(), is(0)) + } + + @Test + def component_registry_with_wildcard_upper_bound_can_be_injected() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleDerivedComponent])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingAllSimpleComponentsUpperBound])) + componentGraph.complete() + + val instance = componentGraph.getInstance(classOf[ComponentTakingAllSimpleComponentsUpperBound]) + assertThat(instance.simpleComponents.allComponents().size(), is(2)) + } + + @Test(expected = classOf[RuntimeException]) + def require_exception_when_injecting_registry_with_unknown_type_variable() { + val clazz = classOf[ComponentTakingAllComponentsWithTypeVariable[_]] + + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleDerivedComponent])) + componentGraph.add(mockComponentNode(clazz)) + componentGraph.complete() + + componentGraph.getInstance(clazz) + } + + @Test + def components_are_shared() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.complete() + + val instance1 = componentGraph.getInstance(classOf[SimpleComponent]) + val instance2 = componentGraph.getInstance(classOf[SimpleComponent]) + assertThat(instance1, sameInstance(instance2)) + } + + @Test + def singleton_components_can_be_injected() { + val componentGraph = new ComponentGraph + val configId = """raw:stringVal "test-value" """ + + componentGraph.add(mockComponentNode(classOf[ComponentTakingComponent])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingConfig], configId)) + componentGraph.add(mockComponentNode(classOf[SimpleComponent2])) + componentGraph.complete() + componentGraph.setAvailableConfigs(Map(keyAndConfig(classOf[TestConfig], configId))) + + val instance = componentGraph.getInstance(classOf[ComponentTakingComponent]) + assertThat(instance.injectedComponent.asInstanceOf[ComponentTakingConfig].config.stringVal(), is("test-value")) + } + + @Test(expected = classOf[RuntimeException]) + def require_error_when_multiple_components_match_a_singleton_dependency() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleDerivedComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingComponent])) + componentGraph.complete() + } + + @Test + def named_component_can_be_injected() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleComponent], key = Names.named("named-test"))) + componentGraph.add(mockComponentNode(classOf[ComponentTakingNamedComponent])) + componentGraph.complete() + } + + @Test + def config_keys_can_be_retrieved() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[ComponentTakingConfig], configId = """raw:stringVal "component1" """"")) + componentGraph.add(mockComponentNode(classOf[ComponentTakingConfig], configId = """raw:stringVal "component2" """"")) + componentGraph.add(new ComponentRegistryNode(classOf[ComponentTakingConfig])) + componentGraph.complete() + + val configKeys = componentGraph.configKeys + assertThat(configKeys.size, is(2)) + + configKeys.foreach{ key => + assertThat(key.getConfigClass, equalTo(classOf[TestConfig])) + assertThat(key.getConfigId.toString, containsString("component")) + } + } + + @Test + def providers_can_be_instantiated() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[ExecutorProvider])) + componentGraph.complete() + + assertNotNull(componentGraph.getInstance(classOf[Executor])) + } + + @Test + def providers_can_be_inherited() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[DerivedExecutorProvider])) + componentGraph.complete() + + assertNotNull(componentGraph.getInstance(classOf[Executor])) + } + + @Test + def providers_can_deliver_a_new_instance_for_each_component() { + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNode(classOf[NewIntProvider])) + componentGraph.complete() + + val instance1 = componentGraph.getInstance(classOf[Int]) + val instance2 = componentGraph.getInstance(classOf[Int]) + assertThat(instance1, not(equalTo(instance2))) + } + + @Test + def providers_can_be_injected_explicitly() { + val componentGraph = new ComponentGraph + + val componentTakingExecutor = mockComponentNode(classOf[ComponentTakingExecutor]) + val executorProvider = mockComponentNode(classOf[ExecutorProvider]) + componentTakingExecutor.inject(executorProvider) + + componentGraph.add(executorProvider) + componentGraph.add(mockComponentNode(classOf[ExecutorProvider])) + + componentGraph.add(componentTakingExecutor) + + componentGraph.complete() + assertNotNull(componentGraph.getInstance(classOf[ComponentTakingExecutor])) + } + + @Test + def global_providers_can_be_injected() { + val componentGraph = new ComponentGraph + + componentGraph.add(mockComponentNode(classOf[ComponentTakingExecutor])) + componentGraph.add(mockComponentNode(classOf[ExecutorProvider])) + componentGraph.add(mockComponentNode(classOf[IntProvider])) + componentGraph.complete() + + assertNotNull(componentGraph.getInstance(classOf[ComponentTakingExecutor])) + } + + @Test(expected = classOf[RuntimeException]) + def throw_if_multiple_global_providers_exist(): Unit = { + val componentGraph = new ComponentGraph + + componentGraph.add(mockComponentNode(classOf[ExecutorProvider])) + componentGraph.add(mockComponentNode(classOf[ExecutorProvider])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingExecutor])) + componentGraph.complete() + } + + @Test + def provider_is_not_used_when_component_of_provided_class_exists() { + val componentGraph = new ComponentGraph + + componentGraph.add(mockComponentNode(classOf[SimpleComponent])) + componentGraph.add(mockComponentNode(classOf[SimpleComponentProviderThatThrows])) + componentGraph.add(mockComponentNode(classOf[ComponentTakingComponent])) + componentGraph.complete() + + val injectedComponent = componentGraph.getInstance(classOf[ComponentTakingComponent]).injectedComponent + assertNotNull(injectedComponent) + } + + //TODO: move + @Test + def check_if_annotation_is_a_binding_annotation() { + import ComponentGraph.isBindingAnnotation + + assertTrue(isBindingAnnotation(Names.named("name"))) + assertFalse(isBindingAnnotation(classOf[Named].getAnnotations.head)) + } + + @Test + def cycles_gives_exception() { + val componentGraph = new ComponentGraph + + def mockNode = mockComponentNode(classOf[ComponentCausingCycle]) + + val node1 = mockNode + val node2 = mockNode + + node1.inject(node2) + node2.inject(node1) + + componentGraph.add(node1) + componentGraph.add(node2) + + try { + componentGraph.complete() + fail("Cycle exception expected.") + } catch { + case e : Throwable => assertThat(e.getMessage, containsString("cycle")) + } + } + + @Test(expected = classOf[IllegalArgumentException]) + def abstract_classes_are_rejected() { + new ComponentNode(ComponentId.fromString("Test"), "", classOf[AbstractClass]) + } + + @Test + def inject_constructor_is_preferred() { + assertThatComponentCanBeCreated(classOf[ComponentWithInjectConstructor]) + } + + @Test + def constructor_with_most_parameters_is_preferred() { + assertThatComponentCanBeCreated(classOf[ComponentWithMultipleConstructors]) + } + + def assertThatComponentCanBeCreated(clazz: Class[AnyRef]) { + val componentGraph = new ComponentGraph + val configId = """raw:stringVal "dummy" """" + + componentGraph.add(mockComponentNode(clazz, configId)) + componentGraph.complete() + + componentGraph.setAvailableConfigs(Map( + keyAndConfig(classOf[TestConfig], configId), + keyAndConfig(classOf[Test2Config], configId))) + + assertNotNull(componentGraph.getInstance(clazz)) + } + + @Test + def require_fallback_to_child_injector() { + val componentGraph = new ComponentGraph + + componentGraph.add(mockComponentNode(classOf[ComponentTakingExecutor])) + + componentGraph.complete(singletonExecutorInjector) + assertNotNull(componentGraph.getInstance(classOf[ComponentTakingExecutor])) + } + + @Test + def child_injector_can_inject_multiple_instances_for_same_key() { + def executorProvider() = Executors.newSingleThreadExecutor() + + val (graphSize, executorA, executorB) = buildGraphWithChildInjector(executorProvider) + + assertThat(graphSize, is(4)) + assertThat(executorA, not(sameInstance(executorB))) + } + + @Test + def components_injected_via_child_injector_can_be_shared() { + val commonExecutor = Executors.newSingleThreadExecutor() + val (graphSize, executorA, executorB) = buildGraphWithChildInjector(() => commonExecutor) + + assertThat(graphSize, is(3)) + assertThat(executorA, sameInstance(executorB)) + } + + def buildGraphWithChildInjector(executorProvider: () => Executor) = { + val childInjector = Guice.createInjector(new AbstractModule { + override def configure() { + bind(classOf[Executor]).toProvider(new GuiceProvider[Executor] { + def get() = executorProvider() + }) + } + }) + + val componentGraph = new ComponentGraph + + def key(name: String) = Key.get(classOf[ComponentTakingExecutor], Names.named(name)) + val keyA = key("A") + val keyB = key("B") + + componentGraph.add(mockComponentNode(keyA)) + componentGraph.add(mockComponentNode(keyB)) + + componentGraph.complete(childInjector) + + (componentGraph.size, componentGraph.getInstance(keyA).executor, componentGraph.getInstance(keyB).executor) + } + + @Test + def providers_can_be_reused() { + def createGraph() = { + val graph = new ComponentGraph() + graph.add(mockComponentNodeWithId(classOf[ExecutorProvider], "dummyId")) + graph.complete() + graph.setAvailableConfigs(Map()) + graph + } + + val oldGraph = createGraph() + val executor = oldGraph.getInstance(classOf[Executor]) + + val newGraph = createGraph() + newGraph.reuseNodes(oldGraph) + + val newExecutor = newGraph.getInstance(classOf[Executor]) + assertThat(executor, sameInstance(newExecutor)) + } + + @Test + def component_id_can_be_injected() { + val componentId: String = "myId:1.2@namespace" + + val componentGraph = new ComponentGraph + componentGraph.add(mockComponentNodeWithId(classOf[ComponentTakingComponentId], componentId)) + componentGraph.complete() + + assertThat(componentGraph.getInstance(classOf[ComponentTakingComponentId]).componentId, + is(ComponentId.fromString(componentId))) + } + + @Test + def rest_api_context_can_be_instantiated() { + val configId = """raw:"" """ + + val clazz = classOf[RestApiContext] + val jerseyNode = new JerseyNode(uniqueComponentId(clazz.getName), configId, clazz, new Osgi {}) + + val componentGraph = new ComponentGraph + componentGraph.add(jerseyNode) + componentGraph.complete() + componentGraph.setAvailableConfigs(Map(keyAndConfig(classOf[JerseyBundlesConfig], configId), + keyAndConfig(classOf[JerseyInjectionConfig], configId))) + + val 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. +object ComponentGraphTest { + var counter = 0 + + + class SimpleComponent extends AbstractComponent + class SimpleComponent2 extends AbstractComponent + class SimpleDerivedComponent extends SimpleComponent + + class ComponentTakingConfig(val config: TestConfig) extends SimpleComponent { + require(config != null) + } + + class ComponentTakingComponent(val injectedComponent: SimpleComponent) extends AbstractComponent { + require(injectedComponent != null) + } + + class ComponentTakingConfigAndComponent(val config: TestConfig, val injectedComponent: SimpleComponent) extends AbstractComponent { + require(config != null) + require(injectedComponent != null) + } + + class ComponentTakingAllSimpleComponents(val simpleComponents: ComponentRegistry[SimpleComponent]) extends AbstractComponent { + require(simpleComponents != null) + } + + class ComponentTakingAllSimpleComponentsUpperBound(val simpleComponents: ComponentRegistry[_ <: SimpleComponent]) + extends AbstractComponent { + + require(simpleComponents != null) + } + + class ComponentTakingAllComponentsWithTypeVariable[COMPONENT <: AbstractComponent](val simpleComponents: ComponentRegistry[COMPONENT]) + extends AbstractComponent { + + require(simpleComponents != null) + } + + class ComponentTakingNamedComponent(@Named("named-test") injectedComponent: SimpleComponent) extends AbstractComponent { + require(injectedComponent != null) + } + + class ComponentCausingCycle(component: ComponentCausingCycle) extends AbstractComponent + + class SimpleComponentProviderThatThrows extends Provider[SimpleComponent] { + def get() = throw new AssertionError("Should never be called.") + def deconstruct() {} + } + + class ExecutorProvider extends Provider[Executor] { + val executor = Executors.newSingleThreadExecutor() + def get() = executor + def deconstruct() { /*TODO */ } + } + + class DerivedExecutorProvider extends ExecutorProvider + + class IntProvider extends Provider[java.lang.Integer] { + def get() = throw new AssertionError("Should never be called.") + def deconstruct() {} + } + + class NewIntProvider extends Provider[Integer] { + var i: Int = 0 + def get() = { + i += 1 + i + } + def deconstruct() {} + } + + class ComponentTakingExecutor(val executor: Executor) extends AbstractComponent { + require(executor != null) + } + + class ComponentWithInjectConstructor private () { + def this(c: TestConfig, c2: Test2Config) = { this(); sys.error("Should not be called") } + @Inject + def this(c: Test2Config) = { this() } + } + + class ComponentWithMultipleConstructors private (dummy : Int) { + def this(c: TestConfig, c2: Test2Config) = { this(0); } + + def this() = { this(0); sys.error("Should not be called") } + def this(c: Test2Config) = { this() } + } + + class ComponentTakingComponentId(val componentId: ComponentId) + + def uniqueComponentId(className: String): ComponentId = { + counter += 1 + ComponentId.fromString(className + counter) + } + + def mockComponentNode(key: Key[_ <: AnyRef]): Node = + mockComponentNode(key.getTypeLiteral.getRawType.asInstanceOf[Class[AnyRef]], key=key.getAnnotation) + + def mockComponentNode(clazz: Class[_ <: AnyRef], configId: String = "", key: JavaAnnotation = null): Node = + new ComponentNode(uniqueComponentId(clazz.getName), configId, clazz, key) + + def mockComponentNodeWithId(clazz: Class[_ <: AnyRef], componentId: String, configId: String = "", key: JavaAnnotation = null): Node = + new ComponentNode(ComponentId.fromString(componentId), configId, clazz, key) + + val singletonExecutorInjector = Guice.createInjector(new AbstractModule { + override def configure() { + bind(classOf[Executor]).toInstance(Executors.newSingleThreadExecutor()) + } + }) + + implicit def makeMatcherCovariant[T, U >: T](matcher: Matcher[T]) : Matcher[U] = matcher.asInstanceOf[Matcher[U]] + + abstract class AbstractClass +} + diff --git a/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.scala b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.scala new file mode 100644 index 00000000000..fcbde13639d --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.scala @@ -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.container.di.componentgraph.core + +import java.util +import java.util.Collections +import com.yahoo.container.di.osgi.OsgiUtil +import org.junit.Test +import org.junit.Assert._ +import org.hamcrest.CoreMatchers.is +import org.hamcrest.Matchers.{contains, containsInAnyOrder} +import org.osgi.framework.wiring.BundleWiring +import scala.collection.JavaConverters._ +import com.yahoo.container.bundle.MockBundle + +/** + * + * @author gjoranv + * @since 5.17 + */ + +class JerseyNodeTest { + + trait WithMockBundle { + object bundle extends MockBundle { + val entry = Map( + "com/foo" -> "Foo.class", + "com/bar" -> "Bar.class)" + ) map { case (packageName, className) => (packageName, packageName + "/" + className)} + + + override def listResources(path: String, ignored: String, options: Int): util.Collection[String] = { + if ((options & BundleWiring.LISTRESOURCES_RECURSE) != 0 && path == "/") entry.values.asJavaCollection + else Collections.singleton(entry(path)) + } + } + + val bundleClasses = bundle.entry.values.toList + } + + @Test + def all_bundle_entries_are_returned_when_no_packages_are_given() { + new WithMockBundle { + val entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Set()).asJavaCollection + assertThat(entries, containsInAnyOrder(bundleClasses: _*)) + } + } + + @Test + def only_bundle_entries_from_the_given_packages_are_returned() { + new WithMockBundle { + val entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Set("com.foo")).asJavaCollection + assertThat(entries, contains(bundle.entry("com/foo"))) + } + } + + @Test + def bundle_info_is_initialized() { + new WithMockBundle { + val bundleInfo = JerseyNode.createBundleInfo(bundle, List()) + 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/scala/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.scala b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.scala new file mode 100644 index 00000000000..e9c0be03a30 --- /dev/null +++ b/container-di/src/test/scala/com/yahoo/container/di/componentgraph/core/ReuseComponentsTest.scala @@ -0,0 +1,249 @@ +// Copyright 2016 Yahoo Inc. 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, AbstractComponent} +import org.junit.Assert._ +import org.hamcrest.CoreMatchers.{is, not, sameInstance, equalTo} +import com.yahoo.vespa.config.ConfigKey +import java.util.concurrent.Executor +import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.{ExecutorProvider, SimpleComponent, SimpleComponent2} +import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.{ComponentTakingConfig, ComponentTakingExecutor, singletonExecutorInjector} +import com.yahoo.container.di.makeClassCovariant +import org.junit.Test +import com.yahoo.config.subscription.ConfigGetter +import com.yahoo.config.test.TestConfig + +/** + * @author gjoranv + * @author tonytv + */ +class ReuseComponentsTest { + import ReuseComponentsTest._ + + @Test + def require_that_component_is_reused_when_componentNode_is_unmodified() { + def reuseAndTest(classToRegister: Class[AnyRef], classToLookup: Class[AnyRef]) { + val graph = buildGraphAndSetNoConfigs(classToRegister) + val instance = getComponent(graph, classToLookup) + + val newGraph = buildGraphAndSetNoConfigs(classToRegister) + newGraph.reuseNodes(graph) + val instance2 = getComponent(newGraph, classToLookup) + + assertThat(instance2, sameInstance(instance)) + } + + reuseAndTest(classOf[SimpleComponent], classOf[SimpleComponent]) + reuseAndTest(classOf[ExecutorProvider], classOf[Executor]) + } + + + @Test(expected = classOf[IllegalStateException]) + def require_that_component_is_not_reused_when_class_is_changed() { + val graph = buildGraphAndSetNoConfigs(classOf[SimpleComponent]) + val instance = getComponent(graph, classOf[SimpleComponent]) + + val newGraph = buildGraphAndSetNoConfigs(classOf[SimpleComponent2]) + newGraph.reuseNodes(graph) + val instance2 = getComponent(newGraph, classOf[SimpleComponent2]) + + assertThat(instance2.getId, is(instance.getId)) + val throwsException = getComponent(newGraph, classOf[SimpleComponent]) + } + + @Test + def require_that_component_is_not_reused_when_config_is_changed() { + def setConfig(graph: ComponentGraph, config: String) { + graph.setAvailableConfigs( + Map(new ConfigKey(classOf[TestConfig], "component") -> + ConfigGetter.getConfig(classOf[TestConfig], """raw: stringVal "%s" """.format(config)))) + } + + val componentClass = classOf[ComponentTakingConfig] + + val graph = buildGraph(componentClass) + setConfig(graph, "oldConfig") + val instance = getComponent(graph, componentClass) + + val newGraph = buildGraph(componentClass) + setConfig(newGraph, "newConfig") + newGraph.reuseNodes(graph) + val instance2 = getComponent(newGraph, componentClass) + + assertThat(instance2, not(sameInstance(instance))) + } + + @Test + def require_that_component_is_not_reused_when_injected_component_is_changed() { + import ComponentGraphTest.{ComponentTakingComponent, ComponentTakingConfig} + + def buildGraph(config: String) = { + val graph = new ComponentGraph + + val rootComponent = mockComponentNode(classOf[ComponentTakingComponent], "root_component") + + val configId = "componentTakingConfigId" + val injectedComponent = mockComponentNode(classOf[ComponentTakingConfig], "injected_component", configId) + + rootComponent.inject(injectedComponent) + + graph.add(rootComponent) + graph.add(injectedComponent) + + graph.complete() + graph.setAvailableConfigs(Map(new ConfigKey(classOf[TestConfig], configId) -> + ConfigGetter.getConfig(classOf[TestConfig], """raw: stringVal "%s" """.format(config)))) + + graph + } + + val oldGraph = buildGraph(config="oldGraph") + val oldInstance = getComponent(oldGraph, classOf[ComponentTakingComponent]) + + val newGraph = buildGraph(config="newGraph") + newGraph.reuseNodes(oldGraph) + val newInstance = getComponent(newGraph, classOf[ComponentTakingComponent]) + + assertThat(newInstance, not(sameInstance(oldInstance))) + } + + @Test + def require_that_component_is_not_reused_when_injected_component_registry_has_one_component_removed() { + import ComponentGraphTest.ComponentTakingAllSimpleComponents + + def buildGraph(useBothInjectedComponents: Boolean) = { + val graph = new ComponentGraph + graph.add(mockComponentNode(classOf[ComponentTakingAllSimpleComponents], "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(classOf[SimpleComponent], "injected_component2")) + graph.add(mockComponentNode(classOf[SimpleComponent], "injected_component1")) + } else { + graph.add(mockComponentNode(classOf[SimpleComponent], "injected_component1")) + } + + graph.complete() + graph.setAvailableConfigs(Map()) + graph + } + + val oldGraph = buildGraph(useBothInjectedComponents = true) + val oldSimpleComponentRegistry = getComponent(oldGraph, classOf[ComponentTakingAllSimpleComponents]).simpleComponents + + val newGraph = buildGraph(useBothInjectedComponents = false) + newGraph.reuseNodes(oldGraph) + val newSimpleComponentRegistry = getComponent(newGraph, classOf[ComponentTakingAllSimpleComponents]).simpleComponents + + assertThat(newSimpleComponentRegistry, not(sameInstance(oldSimpleComponentRegistry))) + } + + @Test + def require_that_injected_component_is_reused_even_when_dependent_component_is_changed() { + import ComponentGraphTest.{ComponentTakingConfigAndComponent, SimpleComponent} + + def buildGraph(config: String) = { + val graph = new ComponentGraph + + val configId = "componentTakingConfigAndComponent" + val rootComponent = mockComponentNode(classOf[ComponentTakingConfigAndComponent], "root_component", configId) + + val injectedComponent = mockComponentNode(classOf[SimpleComponent], "injected_component") + + rootComponent.inject(injectedComponent) + + graph.add(rootComponent) + graph.add(injectedComponent) + + graph.complete() + graph.setAvailableConfigs(Map(new ConfigKey(classOf[TestConfig], configId) -> + ConfigGetter.getConfig(classOf[TestConfig], """raw: stringVal "%s" """.format(config)))) + + graph + } + + val oldGraph = buildGraph(config="oldGraph") + val oldInjectedComponent = getComponent(oldGraph, classOf[SimpleComponent]) + val oldDependentComponent = getComponent(oldGraph, classOf[ComponentTakingConfigAndComponent]) + + val newGraph = buildGraph(config="newGraph") + newGraph.reuseNodes(oldGraph) + val newInjectedComponent = getComponent(newGraph, classOf[SimpleComponent]) + val newDependentComponent = getComponent(newGraph, classOf[ComponentTakingConfigAndComponent]) + + assertThat(newDependentComponent, not(sameInstance(oldDependentComponent))) + assertThat(newInjectedComponent, sameInstance(oldInjectedComponent)) + } + + @Test + def require_that_node_depending_on_guice_node_is_reused() { + def makeGraph = { + val graph = new ComponentGraph + graph.add(mockComponentNode(classOf[ComponentTakingExecutor], "dummyId")) + graph.complete(singletonExecutorInjector) + graph.setAvailableConfigs(Map()) + graph + } + + val getComponentTakingExecutor = getComponent(_: ComponentGraph, classOf[ComponentTakingExecutor]) + + val oldGraph = makeGraph + getComponentTakingExecutor(oldGraph) // Ensure creation of GuiceNode + val newGraph = makeGraph + newGraph.reuseNodes(oldGraph) + assertThat(getComponentTakingExecutor(oldGraph), sameInstance(getComponentTakingExecutor(newGraph))) + } + + @Test + def require_that_node_equals_only_checks_first_level_components_to_inject() { + + def createNodeWithInjectedNodeWithInjectedNode(indirectlyInjectedComponentId: String): Node = { + val targetComponent = mockComponentNode(classOf[SimpleComponent], "target") + val directlyInjectedComponent = mockComponentNode(classOf[SimpleComponent], "directlyInjected") + val indirectlyInjectedComponent = mockComponentNode(classOf[SimpleComponent], indirectlyInjectedComponentId) + directlyInjectedComponent.inject(indirectlyInjectedComponent) + targetComponent.inject(directlyInjectedComponent) + + completeNode(targetComponent) + completeNode(directlyInjectedComponent) + completeNode(indirectlyInjectedComponent) + + targetComponent + } + val targetNode1 = createNodeWithInjectedNodeWithInjectedNode("indirectlyInjected_1") + val targetNode2 = createNodeWithInjectedNodeWithInjectedNode("indirectlyInjected_2") + assertThat(targetNode1, equalTo(targetNode2)) + } + + private def completeNode(node: ComponentNode) { + node.setArguments(Array()) + node.setAvailableConfigs(Map()) + } + + private def buildGraph(componentClass: Class[_ <: AnyRef]) = { + val commonComponentId = "component" + val g = new ComponentGraph + g.add(mockComponentNode(componentClass, commonComponentId, configId = commonComponentId)) + g.complete() + g + } + + private def buildGraphAndSetNoConfigs(componentClass: Class[_ <: AnyRef]) = { + val g = buildGraph(componentClass) + g.setAvailableConfigs(Map()) + g + } +} + +object ReuseComponentsTest { + + def mockComponentNode(clazz: Class[_ <: AnyRef], componentId: String = "", configId: String="") = + new ComponentNode(new ComponentId(componentId), configId, clazz) + + def getComponent[T](graph: ComponentGraph, clazz: Class[T]) = { + graph.getInstance(clazz) + } +} diff --git a/container-di/src/test/vespa-configdef/bootstrap1.def b/container-di/src/test/vespa-configdef/bootstrap1.def new file mode 100644 index 00000000000..3af0c945db8 --- /dev/null +++ b/container-di/src/test/vespa-configdef/bootstrap1.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=config.test + +dummy string default="" diff --git a/container-di/src/test/vespa-configdef/bootstrap2.def b/container-di/src/test/vespa-configdef/bootstrap2.def new file mode 100644 index 00000000000..ba0e8cccd56 --- /dev/null +++ b/container-di/src/test/vespa-configdef/bootstrap2.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=config.test + +dummy string default="" + diff --git a/container-di/src/test/vespa-configdef/components1.def b/container-di/src/test/vespa-configdef/components1.def new file mode 100644 index 00000000000..3af0c945db8 --- /dev/null +++ b/container-di/src/test/vespa-configdef/components1.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=config.test + +dummy string default="" diff --git a/container-di/src/test/vespa-configdef/int.def b/container-di/src/test/vespa-configdef/int.def new file mode 100644 index 00000000000..99fb79d64cf --- /dev/null +++ b/container-di/src/test/vespa-configdef/int.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=config.di + +intVal int default=1 diff --git a/container-di/src/test/vespa-configdef/string.def b/container-di/src/test/vespa-configdef/string.def new file mode 100644 index 00000000000..53585338ee6 --- /dev/null +++ b/container-di/src/test/vespa-configdef/string.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=config.di + +stringVal string default="_default_" diff --git a/container-di/src/test/vespa-configdef/test.def b/container-di/src/test/vespa-configdef/test.def new file mode 100644 index 00000000000..d77b2e9df16 --- /dev/null +++ b/container-di/src/test/vespa-configdef/test.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=config.test + +stringVal string default="default" diff --git a/container-di/src/test/vespa-configdef/test2.def b/container-di/src/test/vespa-configdef/test2.def new file mode 100644 index 00000000000..d77b2e9df16 --- /dev/null +++ b/container-di/src/test/vespa-configdef/test2.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=config.test + +stringVal string default="default" diff --git a/container-di/src/test/vespa-configdef/thread-pool.def b/container-di/src/test/vespa-configdef/thread-pool.def new file mode 100644 index 00000000000..31741e913b6 --- /dev/null +++ b/container-di/src/test/vespa-configdef/thread-pool.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=config.test + +numThreads int |