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