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