summaryrefslogtreecommitdiffstats
path: root/container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala
diff options
context:
space:
mode:
Diffstat (limited to 'container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala')
-rw-r--r--container-di/src/test/scala/com/yahoo/container/di/ContainerTest.scala350
1 files changed, 350 insertions, 0 deletions
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
+}