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