• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.subcomponent.multibindings;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import dagger.Component;
22 import dagger.Module;
23 import dagger.Provides;
24 import dagger.Subcomponent;
25 import dagger.multibindings.IntoSet;
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.Set;
29 import javax.inject.Inject;
30 import javax.inject.Qualifier;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.junit.runners.JUnit4;
34 
35 /**
36  * Regression tests for an issue where subcomponent builder bindings were incorrectly reused from
37  * a parent even if the subcomponent were redeclared on the child component. This manifested via
38  * multibindings, especially since subcomponent builder bindings are special in that we cannot
39  * traverse them to see if they depend on local multibinding contributions.
40  */
41 @RunWith(JUnit4.class)
42 public final class SubcomponentBuilderMultibindingsTest {
43 
44   @Retention(RetentionPolicy.RUNTIME)
45   @Qualifier
46   public @interface ParentFoo{}
47 
48   @Retention(RetentionPolicy.RUNTIME)
49   @Qualifier
50   public @interface ChildFoo{}
51 
52   public static final class Foo {
53     final Set<String> multi;
Foo(Set<String> multi)54     @Inject Foo(Set<String> multi) {
55       this.multi = multi;
56     }
57   }
58 
59   // This tests the case where a subcomponent is installed in both the parent and child component.
60   // In this case, we expect two subcomponents to be generated with the child one including the
61   // child multibinding contribution.
62   public static final class ChildInstallsFloating {
63     @Component(modules = ParentModule.class)
64     public interface Parent {
getParentFoo()65       @ParentFoo Foo getParentFoo();
66 
getChild()67       Child getChild();
68     }
69 
70     @Subcomponent(modules = ChildModule.class)
71     public interface Child {
getChildFoo()72       @ChildFoo Foo getChildFoo();
73     }
74 
75     @Subcomponent
76     public interface FloatingSub {
getFoo()77       Foo getFoo();
78 
79       @Subcomponent.Builder
80       public interface Builder {
build()81         FloatingSub build();
82       }
83     }
84 
85     @Module(subcomponents = FloatingSub.class)
86     public interface ParentModule {
87       @Provides
88       @IntoSet
provideStringMulti()89       static String provideStringMulti() {
90          return "parent";
91       }
92 
93       @Provides
94       @ParentFoo
provideParentFoo(FloatingSub.Builder builder)95       static Foo provideParentFoo(FloatingSub.Builder builder) {
96         return builder.build().getFoo();
97       }
98     }
99 
100     // The subcomponent installation of FloatingSub here is the key difference
101     @Module(subcomponents = FloatingSub.class)
102     public interface ChildModule {
103       @Provides
104       @IntoSet
provideStringMulti()105       static String provideStringMulti() {
106          return "child";
107       }
108 
109       @Provides
110       @ChildFoo
provideChildFoo(FloatingSub.Builder builder)111       static Foo provideChildFoo(FloatingSub.Builder builder) {
112         return builder.build().getFoo();
113       }
114     }
115 
ChildInstallsFloating()116     private ChildInstallsFloating() {}
117   }
118 
119   // This is the same as the above, except this time the child does not install the subcomponent
120   // builder. Here, we expect the child to reuse the parent subcomponent binding (we want to avoid
121   // any mistakes that might implicitly create a new subcomponent relationship) and so therefore
122   // we expect only one subcomponent to be generated in the parent resulting in the child not seeing
123   // the child multibinding contribution.
124   public static final class ChildDoesNotInstallFloating {
125     @Component(modules = ParentModule.class)
126     public interface Parent {
getParentFoo()127       @ParentFoo Foo getParentFoo();
128 
getChild()129       Child getChild();
130     }
131 
132     @Subcomponent(modules = ChildModule.class)
133     public interface Child {
getChildFoo()134       @ChildFoo Foo getChildFoo();
135     }
136 
137     @Subcomponent
138     public interface FloatingSub {
getFoo()139       Foo getFoo();
140 
141       @Subcomponent.Builder
142       public interface Builder {
build()143         FloatingSub build();
144       }
145     }
146 
147     @Module(subcomponents = FloatingSub.class)
148     public interface ParentModule {
149       @Provides
150       @IntoSet
provideStringMulti()151       static String provideStringMulti() {
152          return "parent";
153       }
154 
155       @Provides
156       @ParentFoo
provideParentFoo(FloatingSub.Builder builder)157       static Foo provideParentFoo(FloatingSub.Builder builder) {
158         return builder.build().getFoo();
159       }
160     }
161 
162     // The lack of a subcomponent installation of FloatingSub here is the key difference
163     @Module
164     public interface ChildModule {
165       @Provides
166       @IntoSet
provideStringMulti()167       static String provideStringMulti() {
168          return "child";
169       }
170 
171       @Provides
172       @ChildFoo
provideChildFoo(FloatingSub.Builder builder)173       static Foo provideChildFoo(FloatingSub.Builder builder) {
174         return builder.build().getFoo();
175       }
176     }
177 
ChildDoesNotInstallFloating()178     private ChildDoesNotInstallFloating() {}
179   }
180 
181   // This is similar to the first, except this time the components installs the subcomponent via
182   // factory methods. Here, we expect the child to get a new subcomponent and so should see its
183   // multibinding contribution.
184   public static final class ChildInstallsFloatingFactoryMethod {
185     @Component(modules = ParentModule.class)
186     public interface Parent {
getParentFoo()187       @ParentFoo Foo getParentFoo();
188 
getChild()189       Child getChild();
190 
getFloatingSub()191       FloatingSub getFloatingSub();
192     }
193 
194     @Subcomponent(modules = ChildModule.class)
195     public interface Child {
getChildFoo()196       @ChildFoo Foo getChildFoo();
197 
getFloatingSub()198       FloatingSub getFloatingSub();
199     }
200 
201     @Subcomponent
202     public interface FloatingSub {
getFoo()203       Foo getFoo();
204     }
205 
206     @Module
207     public interface ParentModule {
208       @Provides
209       @IntoSet
provideStringMulti()210       static String provideStringMulti() {
211          return "parent";
212       }
213 
214       @Provides
215       @ParentFoo
provideParentFoo(Parent componentSelf)216       static Foo provideParentFoo(Parent componentSelf) {
217         return componentSelf.getFloatingSub().getFoo();
218       }
219     }
220 
221     @Module
222     public interface ChildModule {
223       @Provides
224       @IntoSet
provideStringMulti()225       static String provideStringMulti() {
226          return "child";
227       }
228 
229       @Provides
230       @ChildFoo
provideChildFoo(Child componentSelf)231       static Foo provideChildFoo(Child componentSelf) {
232         return componentSelf.getFloatingSub().getFoo();
233       }
234     }
235 
ChildInstallsFloatingFactoryMethod()236     private ChildInstallsFloatingFactoryMethod() {}
237   }
238 
239   @Test
testChildInstallsFloating()240   public void testChildInstallsFloating() {
241     ChildInstallsFloating.Parent parentComponent =
242         DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloating_Parent.create();
243     assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
244     assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child");
245   }
246 
247   @Test
testChildDoesNotInstallFloating()248   public void testChildDoesNotInstallFloating() {
249     ChildDoesNotInstallFloating.Parent parentComponent =
250         DaggerSubcomponentBuilderMultibindingsTest_ChildDoesNotInstallFloating_Parent.create();
251     assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
252     // Don't expect the child contribution because the child didn't redeclare the subcomponent
253     // dependency, meaning it intends to just use the subcomponent relationship from the parent
254     // component.
255     assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent");
256   }
257 
258   @Test
testChildInstallsFloatingFactoryMethod()259   public void testChildInstallsFloatingFactoryMethod() {
260     ChildInstallsFloatingFactoryMethod.Parent parentComponent =
261         DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloatingFactoryMethod_Parent.create();
262     assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
263     assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child");
264   }
265 }
266