1 /* 2 * Copyright (C) 2023 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.functional.kotlinsrc.subcomponent.multibindings 18 19 import com.google.common.truth.Truth.assertThat 20 import dagger.Component 21 import dagger.Module 22 import dagger.Provides 23 import dagger.Subcomponent 24 import dagger.multibindings.IntoSet 25 import javax.inject.Inject 26 import javax.inject.Qualifier 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 import org.junit.runners.JUnit4 30 31 /** 32 * Regression tests for an issue where subcomponent builder bindings were incorrectly reused from a 33 * parent even if the subcomponent were redeclared on the child component. This manifested via 34 * multibindings, especially since subcomponent builder bindings are special in that we cannot 35 * traverse them to see if they depend on local multibinding contributions. 36 */ 37 @RunWith(JUnit4::class) 38 class SubcomponentBuilderMultibindingsTest { 39 @Qualifier annotation class ParentFoo 40 41 @Qualifier annotation class ChildFoo 42 43 class Foo @Inject internal constructor(val multi: Set<String>) 44 45 // This tests the case where a subcomponent is installed in both the parent and child component. 46 // In this case, we expect two subcomponents to be generated with the child one including the 47 // child multibinding contribution. 48 class ChildInstallsFloating private constructor() { 49 @Component(modules = [ParentModule::class]) 50 interface Parent { parentFoonull51 @ParentFoo fun parentFoo(): Foo 52 fun child(): Child 53 } 54 55 @Subcomponent(modules = [ChildModule::class]) 56 interface Child { 57 @ChildFoo fun childFoo(): Foo 58 } 59 60 @Subcomponent 61 interface FloatingSub { foonull62 fun foo(): Foo 63 64 @Subcomponent.Builder 65 interface Builder { 66 fun build(): FloatingSub 67 } 68 } 69 70 @Module(subcomponents = [FloatingSub::class]) 71 interface ParentModule { 72 companion object { provideStringMultinull73 @Provides @IntoSet fun provideStringMulti(): String = "parent" 74 75 @Provides 76 @ParentFoo 77 fun provideParentFoo(builder: FloatingSub.Builder): Foo = builder.build().foo() 78 } 79 } 80 81 // The subcomponent installation of FloatingSub here is the key difference 82 @Module(subcomponents = [FloatingSub::class]) 83 interface ChildModule { 84 companion object { 85 @Provides @IntoSet fun provideStringMulti(): String = "child" 86 87 @Provides 88 @ChildFoo 89 fun provideChildFoo(builder: FloatingSub.Builder): Foo = builder.build().foo() 90 } 91 } 92 } 93 94 // This is the same as the above, except this time the child does not install the subcomponent 95 // builder. Here, we expect the child to reuse the parent subcomponent binding (we want to avoid 96 // any mistakes that might implicitly create a new subcomponent relationship) and so therefore 97 // we expect only one subcomponent to be generated in the parent resulting in the child not seeing 98 // the child multibinding contribution. 99 class ChildDoesNotInstallFloating private constructor() { 100 @Component(modules = [ParentModule::class]) 101 interface Parent { parentFoonull102 @ParentFoo fun parentFoo(): Foo 103 fun child(): Child 104 } 105 106 @Subcomponent(modules = [ChildModule::class]) 107 interface Child { 108 @ChildFoo fun childFoo(): Foo 109 } 110 111 @Subcomponent 112 interface FloatingSub { foonull113 fun foo(): Foo 114 115 @Subcomponent.Builder 116 interface Builder { 117 fun build(): FloatingSub 118 } 119 } 120 121 @Module(subcomponents = [FloatingSub::class]) 122 interface ParentModule { 123 companion object { provideStringMultinull124 @Provides @IntoSet fun provideStringMulti(): String = "parent" 125 126 @Provides 127 @ParentFoo 128 fun provideParentFoo(builder: FloatingSub.Builder): Foo = builder.build().foo() 129 } 130 } 131 132 // The lack of a subcomponent installation of FloatingSub here is the key difference 133 @Module 134 interface ChildModule { 135 companion object { 136 @Provides @IntoSet fun provideStringMulti(): String = "child" 137 138 @Provides 139 @ChildFoo 140 fun provideChildFoo(builder: FloatingSub.Builder): Foo = builder.build().foo() 141 } 142 } 143 } 144 145 // This is similar to the first, except this time the components installs the subcomponent via 146 // factory methods. Here, we expect the child to get a new subcomponent and so should see its 147 // multibinding contribution. 148 class ChildInstallsFloatingFactoryMethod private constructor() { 149 @Component(modules = [ParentModule::class]) 150 interface Parent { parentFoonull151 @ParentFoo fun parentFoo(): Foo 152 fun child(): Child 153 fun floatingSub(): FloatingSub 154 } 155 156 @Subcomponent(modules = [ChildModule::class]) 157 interface Child { 158 @ChildFoo fun childFoo(): Foo 159 fun floatingSub(): FloatingSub 160 } 161 162 @Subcomponent 163 interface FloatingSub { foonull164 fun foo(): Foo 165 } 166 167 @Module 168 interface ParentModule { 169 companion object { 170 @Provides @IntoSet fun provideStringMulti(): String = "parent" 171 172 @Provides 173 @ParentFoo 174 fun provideParentFoo(componentSelf: Parent): Foo = componentSelf.floatingSub().foo() 175 } 176 } 177 178 @Module 179 interface ChildModule { 180 companion object { provideStringMultinull181 @Provides @IntoSet fun provideStringMulti(): String = "child" 182 183 @Provides 184 @ChildFoo 185 fun provideChildFoo(componentSelf: Child): Foo = componentSelf.floatingSub().foo() 186 } 187 } 188 } 189 190 @Test 191 fun testChildInstallsFloating() { 192 val parentComponent = 193 DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloating_Parent.create() 194 assertThat(parentComponent.parentFoo().multi).containsExactly("parent") 195 assertThat(parentComponent.child().childFoo().multi).containsExactly("parent", "child") 196 } 197 198 @Test testChildDoesNotInstallFloatingnull199 fun testChildDoesNotInstallFloating() { 200 val parentComponent = 201 DaggerSubcomponentBuilderMultibindingsTest_ChildDoesNotInstallFloating_Parent.create() 202 assertThat(parentComponent.parentFoo().multi).containsExactly("parent") 203 // Don't expect the child contribution because the child didn't redeclare the subcomponent 204 // dependency, meaning it intends to just use the subcomponent relationship from the parent 205 // component. 206 assertThat(parentComponent.child().childFoo().multi).containsExactly("parent") 207 } 208 209 @Test testChildInstallsFloatingFactoryMethodnull210 fun testChildInstallsFloatingFactoryMethod() { 211 val parentComponent = 212 DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloatingFactoryMethod_Parent.create() 213 assertThat(parentComponent.parentFoo().multi).containsExactly("parent") 214 assertThat(parentComponent.child().childFoo().multi).containsExactly("parent", "child") 215 } 216 } 217