1 /* 2 * Copyright (C) 2014 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; 18 19 20 import androidx.room.compiler.processing.util.Source; 21 import com.google.common.collect.ImmutableMap; 22 import dagger.testing.compile.CompilerTests; 23 import dagger.testing.golden.GoldenFileRule; 24 import java.util.Collection; 25 import org.junit.Rule; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 import org.junit.runners.Parameterized; 29 import org.junit.runners.Parameterized.Parameters; 30 31 @RunWith(Parameterized.class) 32 public class ProductionComponentProcessorTest { 33 @Parameters(name = "{0}") parameters()34 public static Collection<Object[]> parameters() { 35 return CompilerMode.TEST_PARAMETERS; 36 } 37 38 private static final Source EXECUTOR_MODULE = 39 CompilerTests.javaSource( 40 "test.ExecutorModule", 41 "package test;", 42 "", 43 "import com.google.common.util.concurrent.MoreExecutors;", 44 "import dagger.Module;", 45 "import dagger.Provides;", 46 "import dagger.producers.Production;", 47 "import java.util.concurrent.Executor;", 48 "", 49 "@Module", 50 "final class ExecutorModule {", 51 " @Provides @Production Executor executor() {", 52 " return MoreExecutors.directExecutor();", 53 " }", 54 "}"); 55 56 @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule(); 57 58 private final CompilerMode compilerMode; 59 ProductionComponentProcessorTest(CompilerMode compilerMode)60 public ProductionComponentProcessorTest(CompilerMode compilerMode) { 61 this.compilerMode = compilerMode; 62 } 63 componentOnConcreteClass()64 @Test public void componentOnConcreteClass() { 65 Source componentFile = 66 CompilerTests.javaSource("test.NotAComponent", 67 "package test;", 68 "", 69 "import dagger.producers.ProductionComponent;", 70 "", 71 "@ProductionComponent", 72 "final class NotAComponent {}"); 73 CompilerTests.daggerCompiler(componentFile) 74 .withProcessingOptions(compilerMode.processorOptions()) 75 .compile( 76 subject -> { 77 subject.hasErrorCount(1); 78 subject.hasErrorContaining( 79 "@ProductionComponent may only be applied to an interface or abstract class"); 80 }); 81 } 82 componentOnEnum()83 @Test public void componentOnEnum() { 84 Source componentFile = 85 CompilerTests.javaSource("test.NotAComponent", 86 "package test;", 87 "", 88 "import dagger.producers.ProductionComponent;", 89 "", 90 "@ProductionComponent", 91 "enum NotAComponent {", 92 " INSTANCE", 93 "}"); 94 CompilerTests.daggerCompiler(componentFile) 95 .withProcessingOptions(compilerMode.processorOptions()) 96 .compile( 97 subject -> { 98 subject.hasErrorCount(1); 99 subject.hasErrorContaining( 100 "@ProductionComponent may only be applied to an interface or abstract class"); 101 }); 102 } 103 componentOnAnnotation()104 @Test public void componentOnAnnotation() { 105 Source componentFile = 106 CompilerTests.javaSource("test.NotAComponent", 107 "package test;", 108 "", 109 "import dagger.producers.ProductionComponent;", 110 "", 111 "@ProductionComponent", 112 "@interface NotAComponent {}"); 113 CompilerTests.daggerCompiler(componentFile) 114 .withProcessingOptions(compilerMode.processorOptions()) 115 .compile( 116 subject -> { 117 subject.hasErrorCount(1); 118 subject.hasErrorContaining( 119 "@ProductionComponent may only be applied to an interface or abstract class"); 120 }); 121 } 122 nonModuleModule()123 @Test public void nonModuleModule() { 124 Source componentFile = 125 CompilerTests.javaSource("test.NotAComponent", 126 "package test;", 127 "", 128 "import dagger.producers.ProductionComponent;", 129 "", 130 "@ProductionComponent(modules = Object.class)", 131 "interface NotAComponent {}"); 132 CompilerTests.daggerCompiler(componentFile) 133 .withProcessingOptions(compilerMode.processorOptions()) 134 .compile( 135 subject -> { 136 subject.hasErrorCount(1); 137 subject.hasErrorContaining("is not annotated with one of @Module, @ProducerModule"); 138 }); 139 } 140 141 @Test dependsOnProductionExecutor()142 public void dependsOnProductionExecutor() throws Exception { 143 Source producerModuleFile = 144 CompilerTests.javaSource( 145 "test.SimpleModule", 146 "package test;", 147 "", 148 "import dagger.producers.ProducerModule;", 149 "import dagger.producers.Produces;", 150 "import dagger.producers.Production;", 151 "import java.util.concurrent.Executor;", 152 "", 153 "@ProducerModule", 154 "final class SimpleModule {", 155 " @Produces String str(@Production Executor executor) {", 156 " return \"\";", 157 " }", 158 "}"); 159 Source componentFile = 160 CompilerTests.javaSource( 161 "test.SimpleComponent", 162 "package test;", 163 "", 164 "import com.google.common.util.concurrent.ListenableFuture;", 165 "import dagger.producers.ProductionComponent;", 166 "import java.util.concurrent.Executor;", 167 "", 168 "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})", 169 "interface SimpleComponent {", 170 " ListenableFuture<String> str();", 171 "", 172 " @ProductionComponent.Builder", 173 " interface Builder {", 174 " SimpleComponent build();", 175 " }", 176 "}"); 177 178 String errorMessage = "String may not depend on the production executor"; 179 CompilerTests.daggerCompiler(EXECUTOR_MODULE, producerModuleFile, componentFile) 180 .withProcessingOptions(compilerMode.processorOptions()) 181 .compile( 182 subject -> { 183 subject.hasErrorCount(1); 184 subject.hasErrorContaining(errorMessage) 185 .onSource(componentFile) 186 .onLineContaining("interface SimpleComponent"); 187 }); 188 189 // Verify that the error is reported on the module when fullBindingGraphValidation is enabled. 190 CompilerTests.daggerCompiler(producerModuleFile) 191 .withProcessingOptions( 192 ImmutableMap.<String, String>builder() 193 .putAll(compilerMode.processorOptions()) 194 .put("dagger.fullBindingGraphValidation", "ERROR") 195 .buildOrThrow()) 196 .compile( 197 subject -> { 198 subject.hasErrorCount(1); 199 subject.hasErrorContaining(errorMessage) 200 .onSource(producerModuleFile) 201 .onLineContaining("class SimpleModule"); 202 }); 203 // TODO(dpb): Report at the binding if enclosed in the module. 204 } 205 206 @Test dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs()207 public void dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs() throws Exception { 208 Source myComponent = 209 CompilerTests.javaSource( 210 "test.MyComponent", 211 "package test;", 212 "", 213 "import dagger.Component;", 214 "", 215 "@Component(modules = MyModule.class)", 216 "interface MyComponent {}"); 217 Source myModule = 218 CompilerTests.javaSource( 219 "test.MyModule", 220 "package test;", 221 "", 222 "import dagger.Component;", 223 "import dagger.Module;", 224 "", 225 "@Module(subcomponents = MyProductionSubcomponent.class)", 226 "interface MyModule {}"); 227 Source myProductionSubcomponent = 228 CompilerTests.javaSource( 229 "test.MyProductionSubcomponent", 230 "package test;", 231 "", 232 "import dagger.producers.ProductionSubcomponent;", 233 "", 234 "@ProductionSubcomponent", 235 "interface MyProductionSubcomponent {", 236 " @ProductionSubcomponent.Builder", 237 " interface Builder {", 238 " MyProductionSubcomponent build();", 239 " }", 240 "}"); 241 242 CompilerTests.daggerCompiler(myComponent, myModule, myProductionSubcomponent) 243 .withProcessingOptions( 244 ImmutableMap.<String, String>builder() 245 .putAll(compilerMode.processorOptions()) 246 .put("dagger.pluginsVisitFullBindingGraphs", "ENABLED") 247 .buildOrThrow()) 248 .compile(subject -> subject.hasErrorCount(0)); 249 } 250 251 @Test simpleComponent()252 public void simpleComponent() throws Exception { 253 Source component = 254 CompilerTests.javaSource( 255 "test.TestClass", 256 "package test;", 257 "", 258 "import com.google.common.util.concurrent.ListenableFuture;", 259 "import com.google.common.util.concurrent.MoreExecutors;", 260 "import dagger.Module;", 261 "import dagger.Provides;", 262 "import dagger.producers.ProducerModule;", 263 "import dagger.producers.Produces;", 264 "import dagger.producers.Production;", 265 "import dagger.producers.ProductionComponent;", 266 "import java.util.concurrent.Executor;", 267 "import javax.inject.Inject;", 268 "", 269 "final class TestClass {", 270 " static final class C {", 271 " @Inject C() {}", 272 " }", 273 "", 274 " interface A {}", 275 " interface B {}", 276 "", 277 " @Module", 278 " static final class BModule {", 279 " @Provides B b(C c) {", 280 " return null;", 281 " }", 282 "", 283 " @Provides @Production Executor executor() {", 284 " return MoreExecutors.directExecutor();", 285 " }", 286 " }", 287 "", 288 " @ProducerModule", 289 " static final class AModule {", 290 " @Produces ListenableFuture<A> a(B b) {", 291 " return null;", 292 " }", 293 " }", 294 "", 295 " @ProductionComponent(modules = {AModule.class, BModule.class})", 296 " interface SimpleComponent {", 297 " ListenableFuture<A> a();", 298 " }", 299 "}"); 300 301 CompilerTests.daggerCompiler(component) 302 .withProcessingOptions(compilerMode.processorOptions()) 303 .compile( 304 subject -> { 305 subject.hasErrorCount(0); 306 subject.generatedSource( 307 goldenFileRule.goldenSource("test/DaggerTestClass_SimpleComponent")); 308 }); 309 } 310 nullableProducersAreNotErrors()311 @Test public void nullableProducersAreNotErrors() { 312 Source component = 313 CompilerTests.javaSource("test.TestClass", 314 "package test;", 315 "", 316 "import com.google.common.util.concurrent.ListenableFuture;", 317 "import com.google.common.util.concurrent.MoreExecutors;", 318 "import dagger.Module;", 319 "import dagger.Provides;", 320 "import dagger.producers.ProducerModule;", 321 "import dagger.producers.Produces;", 322 "import dagger.producers.Production;", 323 "import dagger.producers.ProductionComponent;", 324 "import java.util.concurrent.Executor;", 325 "import javax.annotation.Nullable;", 326 "import javax.inject.Inject;", 327 "", 328 "final class TestClass {", 329 " interface A {}", 330 " interface B {}", 331 " interface C {}", 332 "", 333 " @Module", 334 " static final class CModule {", 335 " @Provides @Nullable C c() {", 336 " return null;", 337 " }", 338 "", 339 " @Provides @Production Executor executor() {", 340 " return MoreExecutors.directExecutor();", 341 " }", 342 " }", 343 "", 344 " @ProducerModule", 345 " static final class ABModule {", 346 " @Produces @Nullable B b(@Nullable C c) {", 347 " return null;", 348 " }", 349 350 " @Produces @Nullable ListenableFuture<A> a(B b) {", // NOTE: B not injected as nullable 351 " return null;", 352 " }", 353 " }", 354 "", 355 " @ProductionComponent(modules = {ABModule.class, CModule.class})", 356 " interface SimpleComponent {", 357 " ListenableFuture<A> a();", 358 " }", 359 "}"); 360 CompilerTests.daggerCompiler(component) 361 .withProcessingOptions(compilerMode.processorOptions()) 362 .compile( 363 subject -> { 364 subject.hasErrorCount(0); 365 subject.hasWarningCount(2); 366 subject.hasWarningContaining("@Nullable on @Produces methods does not do anything") 367 .onSource(component) 368 .onLine(33); 369 subject.hasWarningContaining("@Nullable on @Produces methods does not do anything") 370 .onSource(component) 371 .onLine(36); 372 }); 373 } 374 375 @Test productionScope_injectConstructor()376 public void productionScope_injectConstructor() throws Exception { 377 Source productionScoped = 378 CompilerTests.javaSource( 379 "test.ProductionScoped", 380 "package test;", 381 "", 382 "import dagger.producers.ProductionScope;", 383 "import javax.inject.Inject;", 384 "", 385 "@ProductionScope", 386 "class ProductionScoped {", 387 " @Inject ProductionScoped() {}", 388 "}"); 389 Source parent = 390 CompilerTests.javaSource( 391 "test.Parent", 392 "package test;", 393 "", 394 "import dagger.producers.ProductionComponent;", 395 "", 396 "@ProductionComponent", 397 "interface Parent {", 398 " Child child();", 399 "}"); 400 Source child = 401 CompilerTests.javaSource( 402 "test.Child", 403 "package test;", 404 "", 405 "import dagger.producers.ProductionSubcomponent;", 406 "", 407 "@ProductionSubcomponent", 408 "interface Child {", 409 " ProductionScoped productionScoped();", 410 "}"); 411 412 CompilerTests.daggerCompiler(productionScoped, parent, child) 413 .withProcessingOptions(compilerMode.processorOptions()) 414 .compile( 415 subject -> { 416 subject.hasErrorCount(0); 417 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerParent")); 418 }); 419 } 420 421 @Test requestProducerNodeWithProvider_failsWithNotSupportedError()422 public void requestProducerNodeWithProvider_failsWithNotSupportedError() { 423 Source producerModuleFile = 424 CompilerTests.javaSource( 425 "test.SimpleModule", 426 "package test;", 427 "", 428 "import dagger.producers.ProducerModule;", 429 "import dagger.producers.Produces;", 430 "import javax.inject.Provider;", 431 "import java.util.concurrent.Executor;", 432 "import dagger.producers.Production;", 433 "", 434 "@ProducerModule", 435 "final class SimpleModule {", 436 " @Produces String str(Provider<Integer> num) {", 437 " return \"\";", 438 " }", 439 " @Produces Integer num() { return 1; }", 440 "}"); 441 Source componentFile = 442 CompilerTests.javaSource( 443 "test.SimpleComponent", 444 "package test;", 445 "", 446 "import com.google.common.util.concurrent.ListenableFuture;", 447 "import dagger.producers.ProductionComponent;", 448 "", 449 "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})", 450 "interface SimpleComponent {", 451 " ListenableFuture<String> str();", 452 "", 453 " @ProductionComponent.Builder", 454 " interface Builder {", 455 " SimpleComponent build();", 456 " }", 457 "}"); 458 459 CompilerTests.daggerCompiler(EXECUTOR_MODULE, producerModuleFile, componentFile) 460 .withProcessingOptions(compilerMode.processorOptions()) 461 .compile( 462 subject -> { 463 subject.hasErrorCount(1); 464 subject.hasErrorContaining( 465 "request kind PROVIDER cannot be satisfied by production binding"); 466 }); 467 } 468 469 @Test productionBindingKind_failsIfScoped()470 public void productionBindingKind_failsIfScoped() { 471 Source component = 472 CompilerTests.javaSource( 473 "test.TestComponent", 474 "package test;", 475 "", 476 "import com.google.common.util.concurrent.ListenableFuture;", 477 "import dagger.producers.ProductionComponent;", 478 "", 479 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 480 "interface TestComponent {", 481 " ListenableFuture<String> str();", 482 "}"); 483 Source module = 484 CompilerTests.javaSource( 485 "test.TestModule", 486 "package test;", 487 "", 488 "import dagger.producers.ProducerModule;", 489 "import dagger.producers.Produces;", 490 "import dagger.producers.ProductionScope;", 491 "import javax.inject.Provider;", 492 "import java.util.concurrent.Executor;", 493 "import dagger.producers.Production;", 494 "", 495 "@ProducerModule", 496 "interface TestModule {", 497 " @ProductionScope", 498 " @Produces", 499 " static String provideString() { return \"\"; }", 500 "}"); 501 502 CompilerTests.daggerCompiler(component, module, EXECUTOR_MODULE) 503 .withProcessingOptions(compilerMode.processorOptions()) 504 .compile( 505 subject -> { 506 subject.hasErrorCount(1); 507 subject.hasErrorContaining("@Produces methods cannot be scoped"); 508 }); 509 } 510 511 @Test delegateToProductionBindingKind_failsIfScoped()512 public void delegateToProductionBindingKind_failsIfScoped() { 513 Source component = 514 CompilerTests.javaSource( 515 "test.TestComponent", 516 "package test;", 517 "", 518 "import com.google.common.util.concurrent.ListenableFuture;", 519 "import dagger.producers.ProductionComponent;", 520 "", 521 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 522 "interface TestComponent {", 523 " ListenableFuture<Foo> foo();", 524 "}"); 525 Source module = 526 CompilerTests.javaSource( 527 "test.TestModule", 528 "package test;", 529 "", 530 "import dagger.Binds;", 531 "import dagger.producers.ProducerModule;", 532 "import dagger.producers.Produces;", 533 "import dagger.producers.ProductionScope;", 534 "import javax.inject.Provider;", 535 "import java.util.concurrent.Executor;", 536 "import dagger.producers.Production;", 537 "", 538 "@ProducerModule", 539 "interface TestModule {", 540 " @ProductionScope", 541 " @Binds", 542 " Foo bind(FooImpl impl);", 543 "", 544 " @Produces", 545 " static FooImpl fooImpl() { return new FooImpl(); }", 546 "}"); 547 Source foo = 548 CompilerTests.javaSource( 549 "test.Foo", 550 "package test;", 551 "", 552 "interface Foo {}"); 553 Source fooImpl = 554 CompilerTests.javaSource( 555 "test.FooImpl", 556 "package test;", 557 "", 558 "final class FooImpl implements Foo {}"); 559 560 CompilerTests.daggerCompiler(component, module, foo, fooImpl, EXECUTOR_MODULE) 561 .withProcessingOptions(compilerMode.processorOptions()) 562 .compile( 563 subject -> { 564 subject.hasErrorCount(1); 565 subject.hasErrorContaining( 566 "@ProductionScope @Binds Foo TestModule.bind(FooImpl) cannot be scoped " 567 + "because it delegates to an @Produces method"); 568 }); 569 } 570 571 @Test multipleDelegatesToProductionBindingKind_failsIfScoped()572 public void multipleDelegatesToProductionBindingKind_failsIfScoped() { 573 Source component = 574 CompilerTests.javaSource( 575 "test.TestComponent", 576 "package test;", 577 "", 578 "import com.google.common.util.concurrent.ListenableFuture;", 579 "import dagger.producers.ProductionComponent;", 580 "", 581 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 582 "interface TestComponent {", 583 " ListenableFuture<FooSuper> fooSuper();", 584 "}"); 585 Source module = 586 CompilerTests.javaSource( 587 "test.TestModule", 588 "package test;", 589 "", 590 "import dagger.Binds;", 591 "import dagger.producers.ProducerModule;", 592 "import dagger.producers.Produces;", 593 "import dagger.producers.ProductionScope;", 594 "import javax.inject.Provider;", 595 "import java.util.concurrent.Executor;", 596 "import dagger.producers.Production;", 597 "", 598 "@ProducerModule", 599 "interface TestModule {", 600 " @ProductionScope", 601 " @Binds", 602 " FooSuper bindFooSuper(Foo impl);", 603 "", 604 " @Binds", 605 " Foo bindFoo(FooImpl impl);", 606 "", 607 " @Produces", 608 " static FooImpl fooImpl() { return new FooImpl(); }", 609 "}"); 610 Source fooSuper = 611 CompilerTests.javaSource( 612 "test.FooSuper", 613 "package test;", 614 "", 615 "interface FooSuper {}"); 616 Source foo = 617 CompilerTests.javaSource( 618 "test.Foo", 619 "package test;", 620 "", 621 "interface Foo extends FooSuper {}"); 622 Source fooImpl = 623 CompilerTests.javaSource( 624 "test.FooImpl", 625 "package test;", 626 "", 627 "final class FooImpl implements Foo {}"); 628 629 CompilerTests.daggerCompiler(component, module, fooSuper, foo, fooImpl, EXECUTOR_MODULE) 630 .withProcessingOptions(compilerMode.processorOptions()) 631 .compile( 632 subject -> { 633 subject.hasErrorCount(1); 634 subject.hasErrorContaining( 635 "@ProductionScope @Binds FooSuper TestModule.bindFooSuper(Foo) cannot be scoped " 636 + "because it delegates to an @Produces method"); 637 }); 638 } 639 } 640