1 /* 2 * Copyright (C) 2021 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.internal.codegen.bindinggraphvalidation; 18 19 import androidx.room.compiler.processing.util.Source; 20 import com.google.common.collect.ImmutableList; 21 import dagger.internal.codegen.CompilerMode; 22 import dagger.testing.compile.CompilerTests; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.junit.runners.Parameterized; 26 import org.junit.runners.Parameterized.Parameters; 27 28 @RunWith(Parameterized.class) 29 public class SetMultibindingValidationTest { 30 @Parameters(name = "{0}") parameters()31 public static ImmutableList<Object[]> parameters() { 32 return CompilerMode.TEST_PARAMETERS; 33 } 34 35 private final CompilerMode compilerMode; 36 SetMultibindingValidationTest(CompilerMode compilerMode)37 public SetMultibindingValidationTest(CompilerMode compilerMode) { 38 this.compilerMode = compilerMode; 39 } 40 41 private static final Source FOO = 42 CompilerTests.javaSource( 43 "test.Foo", 44 "package test;", 45 "", 46 "public interface Foo {}"); 47 48 private static final Source FOO_IMPL = 49 CompilerTests.javaSource( 50 "test.FooImpl", 51 "package test;", 52 "", 53 "import javax.inject.Inject;", 54 "", 55 "public final class FooImpl implements Foo {", 56 " @Inject FooImpl() {}", 57 "}"); 58 testMultipleSetBindingsToSameFoo()59 @Test public void testMultipleSetBindingsToSameFoo() { 60 Source module = 61 CompilerTests.javaSource( 62 "test.TestModule", 63 "package test;", 64 "", 65 "import dagger.Binds;", 66 "import dagger.multibindings.IntoSet;", 67 "import javax.inject.Inject;", 68 "", 69 "@dagger.Module", 70 "interface TestModule {", 71 " @Binds @IntoSet Foo bindFoo(FooImpl impl);", 72 "", 73 " @Binds @IntoSet Foo bindFooAgain(FooImpl impl);", 74 "}"); 75 Source component = 76 CompilerTests.javaSource( 77 "test.TestComponent", 78 "package test;", 79 "", 80 "import dagger.Component;", 81 "import java.util.Set;", 82 "", 83 "@Component(modules = TestModule.class)", 84 "interface TestComponent {", 85 " Set<Foo> setOfFoo();", 86 "}"); 87 CompilerTests.daggerCompiler(FOO, FOO_IMPL, module, component) 88 .withProcessingOptions(compilerMode.processorOptions()) 89 .compile( 90 subject -> { 91 subject.hasErrorCount(1); 92 subject.hasErrorContaining( 93 "Multiple set contributions into Set<Foo> for the same contribution key: " 94 + "FooImpl"); 95 }); 96 } 97 98 // Regression test for b/316582741 to ensure the duplicate binding gets reported rather than 99 // causing a crash. testSetBindingsToDuplicateBinding()100 @Test public void testSetBindingsToDuplicateBinding() { 101 Source module = 102 CompilerTests.javaSource( 103 "test.TestModule", 104 "package test;", 105 "", 106 "import dagger.Binds;", 107 "import dagger.Module;", 108 "import dagger.Provides;", 109 "import dagger.multibindings.IntoSet;", 110 "", 111 "@Module", 112 "interface TestModule {", 113 " @Binds @IntoSet Foo bindFoo(FooImpl impl);", 114 "", 115 " @Provides static FooImpl provideFooImpl() { return null; }", 116 "", 117 " @Provides static FooImpl provideFooImplAgain() { return null; }", 118 "}"); 119 Source component = 120 CompilerTests.javaSource( 121 "test.TestComponent", 122 "package test;", 123 "", 124 "import dagger.Component;", 125 "import java.util.Set;", 126 "", 127 "@Component(modules = TestModule.class)", 128 "interface TestComponent {", 129 " Set<Foo> setOfFoo();", 130 "}"); 131 CompilerTests.daggerCompiler(FOO, FOO_IMPL, module, component) 132 .withProcessingOptions(compilerMode.processorOptions()) 133 .compile( 134 subject -> { 135 subject.hasErrorCount(1); 136 subject.hasErrorContaining("FooImpl is bound multiple times"); 137 }); 138 } 139 testSetBindingsToMissingBinding()140 @Test public void testSetBindingsToMissingBinding() { 141 Source module = 142 CompilerTests.javaSource( 143 "test.TestModule", 144 "package test;", 145 "", 146 "import dagger.Binds;", 147 "import dagger.Module;", 148 "import dagger.multibindings.IntoSet;", 149 "", 150 "@Module", 151 "interface TestModule {", 152 " @Binds @IntoSet Foo bindFoo(MissingFooImpl impl);", 153 "", 154 " static class MissingFooImpl implements Foo {}", 155 "}"); 156 Source component = 157 CompilerTests.javaSource( 158 "test.TestComponent", 159 "package test;", 160 "", 161 "import dagger.Component;", 162 "import java.util.Set;", 163 "", 164 "@Component(modules = TestModule.class)", 165 "interface TestComponent {", 166 " Set<Foo> setOfFoo();", 167 "}"); 168 CompilerTests.daggerCompiler(FOO, module, component) 169 .withProcessingOptions(compilerMode.processorOptions()) 170 .compile( 171 subject -> { 172 subject.hasErrorCount(1); 173 subject.hasErrorContaining("MissingFooImpl cannot be provided"); 174 }); 175 } 176 testMultipleSetBindingsToSameFooThroughMultipleBinds()177 @Test public void testMultipleSetBindingsToSameFooThroughMultipleBinds() { 178 Source module = 179 CompilerTests.javaSource( 180 "test.TestModule", 181 "package test;", 182 "", 183 "import dagger.Binds;", 184 "import dagger.multibindings.IntoSet;", 185 "import javax.inject.Inject;", 186 "", 187 "@dagger.Module", 188 "interface TestModule {", 189 " @Binds @IntoSet Object bindObject(FooImpl impl);", 190 "", 191 " @Binds @IntoSet Object bindObjectAgain(Foo impl);", 192 "", 193 " @Binds Foo bindFoo(FooImpl impl);", 194 "}"); 195 Source component = 196 CompilerTests.javaSource( 197 "test.TestComponent", 198 "package test;", 199 "", 200 "import dagger.Component;", 201 "import java.util.Set;", 202 "", 203 "@Component(modules = TestModule.class)", 204 "interface TestComponent {", 205 " Set<Object> setOfObject();", 206 "}"); 207 CompilerTests.daggerCompiler(FOO, FOO_IMPL, module, component) 208 .withProcessingOptions(compilerMode.processorOptions()) 209 .compile( 210 subject -> { 211 subject.hasErrorCount(1); 212 subject.hasErrorContaining( 213 "Multiple set contributions into Set<Object> for the same contribution key: " 214 + "FooImpl"); 215 }); 216 } 217 testMultipleSetBindingsViaElementsIntoSet()218 @Test public void testMultipleSetBindingsViaElementsIntoSet() { 219 Source module = 220 CompilerTests.javaSource( 221 "test.TestModule", 222 "package test;", 223 "", 224 "import dagger.Binds;", 225 "import dagger.Provides;", 226 "import dagger.multibindings.ElementsIntoSet;", 227 "import java.util.HashSet;", 228 "import java.util.Set;", 229 "import javax.inject.Inject;", 230 "import javax.inject.Qualifier;", 231 "", 232 "@dagger.Module", 233 "interface TestModule {", 234 "", 235 " @Qualifier", 236 " @interface Internal {}", 237 "", 238 " @Provides @Internal static Set<Foo> provideSet() { return new HashSet<>(); }", 239 "", 240 " @Binds @ElementsIntoSet Set<Foo> bindSet(@Internal Set<Foo> fooSet);", 241 "", 242 " @Binds @ElementsIntoSet Set<Foo> bindSetAgain(@Internal Set<Foo> fooSet);", 243 "}"); 244 Source component = 245 CompilerTests.javaSource( 246 "test.TestComponent", 247 "package test;", 248 "", 249 "import dagger.Component;", 250 "import java.util.Set;", 251 "", 252 "@Component(modules = TestModule.class)", 253 "interface TestComponent {", 254 " Set<Foo> setOfFoo();", 255 "}"); 256 CompilerTests.daggerCompiler(FOO, module, component) 257 .withProcessingOptions(compilerMode.processorOptions()) 258 .compile( 259 subject -> { 260 subject.hasErrorCount(1); 261 subject.hasErrorContaining( 262 "Multiple set contributions into Set<Foo> for the same contribution key: " 263 + "@TestModule.Internal Set<Foo>"); 264 }); 265 } 266 testMultipleSetBindingsToSameFooSubcomponents()267 @Test public void testMultipleSetBindingsToSameFooSubcomponents() { 268 Source parentModule = 269 CompilerTests.javaSource( 270 "test.ParentModule", 271 "package test;", 272 "", 273 "import dagger.Binds;", 274 "import dagger.multibindings.IntoSet;", 275 "import javax.inject.Inject;", 276 "", 277 "@dagger.Module", 278 "interface ParentModule {", 279 " @Binds @IntoSet Foo bindFoo(FooImpl impl);", 280 "}"); 281 Source childModule = 282 CompilerTests.javaSource( 283 "test.ChildModule", 284 "package test;", 285 "", 286 "import dagger.Binds;", 287 "import dagger.multibindings.IntoSet;", 288 "import javax.inject.Inject;", 289 "", 290 "@dagger.Module", 291 "interface ChildModule {", 292 " @Binds @IntoSet Foo bindFoo(FooImpl impl);", 293 "}"); 294 Source parentComponent = 295 CompilerTests.javaSource( 296 "test.ParentComponent", 297 "package test;", 298 "", 299 "import dagger.Component;", 300 "import java.util.Set;", 301 "", 302 "@Component(modules = ParentModule.class)", 303 "interface ParentComponent {", 304 " Set<Foo> setOfFoo();", 305 " ChildComponent child();", 306 "}"); 307 Source childComponent = 308 CompilerTests.javaSource( 309 "test.ChildComponent", 310 "package test;", 311 "", 312 "import dagger.Subcomponent;", 313 "import java.util.Set;", 314 "", 315 "@Subcomponent(modules = ChildModule.class)", 316 "interface ChildComponent {", 317 " Set<Foo> setOfFoo();", 318 "}"); 319 CompilerTests.daggerCompiler( 320 FOO, FOO_IMPL, parentModule, childModule, parentComponent, childComponent) 321 .withProcessingOptions(compilerMode.processorOptions()) 322 .compile( 323 subject -> { 324 subject.hasErrorCount(1); 325 subject.hasErrorContaining( 326 "Multiple set contributions into Set<Foo> for the same contribution key: " 327 + "FooImpl"); 328 subject.hasErrorContaining("ParentComponent → ChildComponent"); 329 }); 330 } 331 testMultipleSetBindingsToSameKeyButDifferentBindings()332 @Test public void testMultipleSetBindingsToSameKeyButDifferentBindings() { 333 // Use an impl with local multibindings to create different bindings. We still want this to fail 334 // even though there are separate bindings because it is likely an unintentional error anyway. 335 Source fooImplWithMult = 336 CompilerTests.javaSource( 337 "test.FooImplWithMult", 338 "package test;", 339 "", 340 "import java.util.Set;", 341 "import javax.inject.Inject;", 342 "", 343 "public final class FooImplWithMult implements Foo {", 344 " @Inject FooImplWithMult(Set<Long> longSet) {}", 345 "}"); 346 // Scoping the @Binds is necessary to ensure it goes to different bindings 347 Source parentModule = 348 CompilerTests.javaSource( 349 "test.ParentModule", 350 "package test;", 351 "", 352 "import dagger.Binds;", 353 "import dagger.Provides;", 354 "import dagger.multibindings.IntoSet;", 355 "import javax.inject.Inject;", 356 "import javax.inject.Singleton;", 357 "", 358 "@dagger.Module", 359 "interface ParentModule {", 360 " @Singleton", 361 " @Binds @IntoSet Foo bindFoo(FooImplWithMult impl);", 362 "", 363 " @Provides @IntoSet static Long provideLong() {", 364 " return 0L;", 365 " }", 366 "}"); 367 Source childModule = 368 CompilerTests.javaSource( 369 "test.ChildModule", 370 "package test;", 371 "", 372 "import dagger.Binds;", 373 "import dagger.Provides;", 374 "import dagger.multibindings.IntoSet;", 375 "import javax.inject.Inject;", 376 "", 377 "@dagger.Module", 378 "interface ChildModule {", 379 " @Binds @IntoSet Foo bindFoo(FooImplWithMult impl);", 380 "", 381 " @Provides @IntoSet static Long provideLong() {", 382 " return 1L;", 383 " }", 384 "}"); 385 Source parentComponent = 386 CompilerTests.javaSource( 387 "test.ParentComponent", 388 "package test;", 389 "", 390 "import dagger.Component;", 391 "import java.util.Set;", 392 "import javax.inject.Singleton;", 393 "", 394 "@Singleton", 395 "@Component(modules = ParentModule.class)", 396 "interface ParentComponent {", 397 " Set<Foo> setOfFoo();", 398 " ChildComponent child();", 399 "}"); 400 Source childComponent = 401 CompilerTests.javaSource( 402 "test.ChildComponent", 403 "package test;", 404 "", 405 "import dagger.Subcomponent;", 406 "import java.util.Set;", 407 "", 408 "@Subcomponent(modules = ChildModule.class)", 409 "interface ChildComponent {", 410 " Set<Foo> setOfFoo();", 411 "}"); 412 CompilerTests.daggerCompiler( 413 FOO, fooImplWithMult, parentModule, childModule, parentComponent, childComponent) 414 .withProcessingOptions(compilerMode.processorOptions()) 415 .compile( 416 subject -> { 417 subject.hasErrorCount(1); 418 subject.hasErrorContaining( 419 "Multiple set contributions into Set<Foo> for the same contribution key: " 420 + "FooImplWithMult"); 421 subject.hasErrorContaining("ParentComponent → ChildComponent"); 422 }); 423 } 424 } 425