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 import androidx.room.compiler.processing.util.Source; 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.collect.ImmutableMap; 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 /** Producer-specific validation tests. */ 29 @RunWith(Parameterized.class) 30 public class ProductionGraphValidationTest { 31 @Parameters(name = "{0}") parameters()32 public static ImmutableList<Object[]> parameters() { 33 return CompilerMode.TEST_PARAMETERS; 34 } 35 36 private final CompilerMode compilerMode; 37 ProductionGraphValidationTest(CompilerMode compilerMode)38 public ProductionGraphValidationTest(CompilerMode compilerMode) { 39 this.compilerMode = compilerMode; 40 } 41 42 private static final Source EXECUTOR_MODULE = 43 CompilerTests.javaSource( 44 "test.ExecutorModule", 45 "package test;", 46 "", 47 "import com.google.common.util.concurrent.MoreExecutors;", 48 "import dagger.Module;", 49 "import dagger.Provides;", 50 "import dagger.producers.Production;", 51 "import java.util.concurrent.Executor;", 52 "", 53 "@Module", 54 "class ExecutorModule {", 55 " @Provides @Production Executor executor() {", 56 " return MoreExecutors.directExecutor();", 57 " }", 58 "}"); 59 componentWithUnprovidedInput()60 @Test public void componentWithUnprovidedInput() { 61 Source component = 62 CompilerTests.javaSource( 63 "test.MyComponent", 64 "package test;", 65 "", 66 "import com.google.common.util.concurrent.ListenableFuture;", 67 "import dagger.producers.ProductionComponent;", 68 "", 69 "@ProductionComponent(modules = {ExecutorModule.class, FooModule.class})", 70 "interface MyComponent {", 71 " ListenableFuture<Foo> getFoo();", 72 "}"); 73 Source module = 74 CompilerTests.javaSource( 75 "test.FooModule", 76 "package test;", 77 "", 78 "import dagger.producers.ProducerModule;", 79 "import dagger.producers.Produces;", 80 "", 81 "@ProducerModule", 82 "class FooModule {", 83 " @Produces Foo foo(Bar bar) {", 84 " return null;", 85 " }", 86 "}"); 87 Source foo = 88 CompilerTests.javaSource( 89 "test.Foo", 90 "package test;", 91 "", 92 "class Foo {}"); 93 Source bar = 94 CompilerTests.javaSource( 95 "test.Bar", 96 "package test;", 97 "", 98 "class Bar {}"); 99 CompilerTests.daggerCompiler(EXECUTOR_MODULE, module, component, foo, bar) 100 .withProcessingOptions(compilerMode.processorOptions()) 101 .compile( 102 subject -> { 103 subject.hasErrorCount(1); 104 subject.hasErrorContaining( 105 "Bar cannot be provided without an @Inject constructor or an @Provides- or " 106 + "@Produces-annotated method.") 107 .onSource(component) 108 .onLineContaining("interface MyComponent"); 109 }); 110 } 111 componentProductionWithNoDependencyChain()112 @Test public void componentProductionWithNoDependencyChain() { 113 Source component = 114 CompilerTests.javaSource( 115 "test.TestClass", 116 "package test;", 117 "", 118 "import com.google.common.util.concurrent.ListenableFuture;", 119 "import dagger.producers.ProductionComponent;", 120 "", 121 "final class TestClass {", 122 " interface A {}", 123 "", 124 " @ProductionComponent(modules = ExecutorModule.class)", 125 " interface AComponent {", 126 " ListenableFuture<A> getA();", 127 " }", 128 "}"); 129 130 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 131 .withProcessingOptions(compilerMode.processorOptions()) 132 .compile( 133 subject -> { 134 subject.hasErrorCount(1); 135 subject.hasErrorContaining( 136 "TestClass.A cannot be provided without an @Provides- or @Produces-annotated " 137 + "method.") 138 .onSource(component) 139 .onLineContaining("interface AComponent"); 140 }); 141 } 142 provisionDependsOnProduction()143 @Test public void provisionDependsOnProduction() { 144 Source component = 145 CompilerTests.javaSource( 146 "test.TestClass", 147 "package test;", 148 "", 149 "import com.google.common.util.concurrent.ListenableFuture;", 150 "import dagger.Provides;", 151 "import dagger.producers.ProducerModule;", 152 "import dagger.producers.Produces;", 153 "import dagger.producers.ProductionComponent;", 154 "", 155 "final class TestClass {", 156 " interface A {}", 157 " interface B {}", 158 "", 159 " @ProducerModule(includes = BModule.class)", 160 " final class AModule {", 161 " @Provides A a(B b) {", 162 " return null;", 163 " }", 164 " }", 165 "", 166 " @ProducerModule", 167 " final class BModule {", 168 " @Produces ListenableFuture<B> b() {", 169 " return null;", 170 " }", 171 " }", 172 "", 173 " @ProductionComponent(modules = {ExecutorModule.class, AModule.class})", 174 " interface AComponent {", 175 " ListenableFuture<A> getA();", 176 " }", 177 "}"); 178 179 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 180 .withProcessingOptions(compilerMode.processorOptions()) 181 .compile( 182 subject -> { 183 subject.hasErrorCount(1); 184 subject.hasErrorContaining( 185 "TestClass.A is a provision, which cannot depend on a production.") 186 .onSource(component) 187 .onLineContaining("interface AComponent"); 188 }); 189 190 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 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( 200 "TestClass.A is a provision, which cannot depend on a production.") 201 .onSource(component) 202 .onLineContaining("class AModule"); 203 }); 204 } 205 provisionEntryPointDependsOnProduction()206 @Test public void provisionEntryPointDependsOnProduction() { 207 Source component = 208 CompilerTests.javaSource( 209 "test.TestClass", 210 "package test;", 211 "", 212 "import com.google.common.util.concurrent.ListenableFuture;", 213 "import dagger.producers.ProducerModule;", 214 "import dagger.producers.Produces;", 215 "import dagger.producers.ProductionComponent;", 216 "", 217 "final class TestClass {", 218 " interface A {}", 219 "", 220 " @ProducerModule", 221 " static final class AModule {", 222 " @Produces ListenableFuture<A> a() {", 223 " return null;", 224 " }", 225 " }", 226 "", 227 " @ProductionComponent(modules = {ExecutorModule.class, AModule.class})", 228 " interface AComponent {", 229 " A getA();", 230 " }", 231 "}"); 232 233 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 234 .withProcessingOptions(compilerMode.processorOptions()) 235 .compile( 236 subject -> { 237 subject.hasErrorCount(1); 238 subject.hasErrorContaining( 239 "TestClass.A is a provision entry-point, which cannot depend on a " 240 + "production.") 241 .onSource(component) 242 .onLineContaining("interface AComponent"); 243 }); 244 } 245 246 @Test providingMultibindingWithProductions()247 public void providingMultibindingWithProductions() { 248 Source component = 249 CompilerTests.javaSource( 250 "test.TestClass", 251 "package test;", 252 "", 253 "import com.google.common.util.concurrent.ListenableFuture;", 254 "import dagger.Module;", 255 "import dagger.Provides;", 256 "import dagger.multibindings.IntoMap;", 257 "import dagger.multibindings.StringKey;", 258 "import dagger.producers.ProducerModule;", 259 "import dagger.producers.Produces;", 260 "import dagger.producers.ProductionComponent;", 261 "import java.util.Map;", 262 "import javax.inject.Provider;", 263 "", 264 "final class TestClass {", 265 " interface A {}", 266 " interface B {}", 267 "", 268 " @Module", 269 " static final class AModule {", 270 " @Provides static A a(Map<String, Provider<Object>> map) {", 271 " return null;", 272 " }", 273 "", 274 " @Provides @IntoMap @StringKey(\"a\") static Object aEntry() {", 275 " return \"a\";", 276 " }", 277 " }", 278 "", 279 " @ProducerModule", 280 " static final class BModule {", 281 " @Produces static B b(A a) {", 282 " return null;", 283 " }", 284 "", 285 " @Produces @IntoMap @StringKey(\"b\") static Object bEntry() {", 286 " return \"b\";", 287 " }", 288 " }", 289 "", 290 " @ProductionComponent(", 291 " modules = {ExecutorModule.class, AModule.class, BModule.class})", 292 " interface AComponent {", 293 " ListenableFuture<B> b();", 294 " }", 295 "}"); 296 297 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 298 .withProcessingOptions(compilerMode.processorOptions()) 299 .compile( 300 subject -> { 301 subject.hasErrorCount(1); 302 subject.hasErrorContaining( 303 "TestClass.A is a provision, which cannot depend on a production") 304 .onSource(component) 305 .onLineContaining("interface AComponent"); 306 }); 307 } 308 309 @Test monitoringDependsOnUnboundType()310 public void monitoringDependsOnUnboundType() { 311 Source component = 312 CompilerTests.javaSource( 313 "test.TestClass", 314 "package test;", 315 "", 316 "import com.google.common.util.concurrent.ListenableFuture;", 317 "import dagger.Module;", 318 "import dagger.Provides;", 319 "import dagger.multibindings.IntoSet;", 320 "import dagger.producers.ProducerModule;", 321 "import dagger.producers.Produces;", 322 "import dagger.producers.ProductionComponent;", 323 "import dagger.producers.monitoring.ProductionComponentMonitor;", 324 "", 325 "final class TestClass {", 326 " interface A {}", 327 "", 328 " @Module", 329 " final class MonitoringModule {", 330 " @Provides @IntoSet", 331 " ProductionComponentMonitor.Factory monitorFactory(A unbound) {", 332 " return null;", 333 " }", 334 " }", 335 "", 336 " @ProducerModule", 337 " final class StringModule {", 338 " @Produces ListenableFuture<String> str() {", 339 " return null;", 340 " }", 341 " }", 342 "", 343 " @ProductionComponent(", 344 " modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}", 345 " )", 346 " interface StringComponent {", 347 " ListenableFuture<String> getString();", 348 " }", 349 "}"); 350 351 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 352 .withProcessingOptions(compilerMode.processorOptions()) 353 .compile( 354 subject -> { 355 subject.hasErrorCount(1); 356 subject.hasErrorContaining( 357 "TestClass.A cannot be provided without an @Provides-annotated method.") 358 .onSource(component) 359 .onLineContaining("interface StringComponent"); 360 }); 361 } 362 363 @Test monitoringDependsOnProduction()364 public void monitoringDependsOnProduction() { 365 Source component = 366 CompilerTests.javaSource( 367 "test.TestClass", 368 "package test;", 369 "", 370 "import com.google.common.util.concurrent.ListenableFuture;", 371 "import dagger.Module;", 372 "import dagger.Provides;", 373 "import dagger.multibindings.IntoSet;", 374 "import dagger.producers.ProducerModule;", 375 "import dagger.producers.Produces;", 376 "import dagger.producers.ProductionComponent;", 377 "import dagger.producers.monitoring.ProductionComponentMonitor;", 378 "", 379 "final class TestClass {", 380 " interface A {}", 381 "", 382 " @Module", 383 " final class MonitoringModule {", 384 " @Provides @IntoSet ProductionComponentMonitor.Factory monitorFactory(A a) {", 385 " return null;", 386 " }", 387 " }", 388 "", 389 " @ProducerModule", 390 " final class StringModule {", 391 " @Produces A a() {", 392 " return null;", 393 " }", 394 "", 395 " @Produces ListenableFuture<String> str() {", 396 " return null;", 397 " }", 398 " }", 399 "", 400 " @ProductionComponent(", 401 " modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}", 402 " )", 403 " interface StringComponent {", 404 " ListenableFuture<String> getString();", 405 " }", 406 "}"); 407 408 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component) 409 .withProcessingOptions(compilerMode.processorOptions()) 410 .compile( 411 subject -> { 412 subject.hasErrorCount(1); 413 subject.hasErrorContaining( 414 "Set<ProductionComponentMonitor.Factory>" 415 + " TestClass.MonitoringModule#monitorFactory is a provision," 416 + " which cannot depend on a production.") 417 .onSource(component) 418 .onLineContaining("interface StringComponent"); 419 }); 420 } 421 422 @Test cycleNotBrokenByMap()423 public void cycleNotBrokenByMap() { 424 Source component = 425 CompilerTests.javaSource( 426 "test.TestComponent", 427 "package test;", 428 "", 429 "import com.google.common.util.concurrent.ListenableFuture;", 430 "import dagger.producers.ProductionComponent;", 431 "", 432 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 433 "interface TestComponent {", 434 " ListenableFuture<String> string();", 435 "}"); 436 Source module = 437 CompilerTests.javaSource( 438 "test.TestModule", 439 "package test;", 440 "", 441 "import dagger.producers.ProducerModule;", 442 "import dagger.producers.Produces;", 443 "import dagger.multibindings.IntoMap;", 444 "import dagger.multibindings.StringKey;", 445 "import java.util.Map;", 446 "", 447 "@ProducerModule", 448 "final class TestModule {", 449 " @Produces static String string(Map<String, String> map) {", 450 " return \"string\";", 451 " }", 452 "", 453 " @Produces @IntoMap @StringKey(\"key\")", 454 " static String entry(String string) {", 455 " return string;", 456 " }", 457 "}"); 458 459 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component, module) 460 .withProcessingOptions(compilerMode.processorOptions()) 461 .compile( 462 subject -> { 463 subject.hasErrorCount(1); 464 subject.hasErrorContaining("cycle") 465 .onSource(component) 466 .onLineContaining("interface TestComponent"); 467 }); 468 } 469 470 @Test cycleNotBrokenByProducerMap()471 public void cycleNotBrokenByProducerMap() { 472 Source component = 473 CompilerTests.javaSource( 474 "test.TestComponent", 475 "package test;", 476 "", 477 "import com.google.common.util.concurrent.ListenableFuture;", 478 "import dagger.producers.ProductionComponent;", 479 "", 480 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 481 "interface TestComponent {", 482 " ListenableFuture<String> string();", 483 "}"); 484 Source module = 485 CompilerTests.javaSource( 486 "test.TestModule", 487 "package test;", 488 "", 489 "import dagger.producers.Producer;", 490 "import dagger.producers.ProducerModule;", 491 "import dagger.producers.Produces;", 492 "import dagger.multibindings.StringKey;", 493 "import dagger.multibindings.IntoMap;", 494 "import java.util.Map;", 495 "", 496 "@ProducerModule", 497 "final class TestModule {", 498 " @Produces static String string(Map<String, Producer<String>> map) {", 499 " return \"string\";", 500 " }", 501 "", 502 " @Produces @IntoMap @StringKey(\"key\")", 503 " static String entry(String string) {", 504 " return string;", 505 " }", 506 "}"); 507 508 CompilerTests.daggerCompiler(EXECUTOR_MODULE, component, module) 509 .withProcessingOptions(compilerMode.processorOptions()) 510 .compile( 511 subject -> { 512 subject.hasErrorCount(1); 513 subject.hasErrorContaining("cycle") 514 .onSource(component) 515 .onLineContaining("interface TestComponent"); 516 }); 517 } 518 519 @Test componentWithBadModule()520 public void componentWithBadModule() { 521 Source badModule = 522 CompilerTests.javaSource( 523 "test.BadModule", 524 "package test;", 525 "", 526 "import dagger.BindsOptionalOf;", 527 "import dagger.multibindings.Multibinds;", 528 "import dagger.Module;", 529 "import java.util.Set;", 530 "", 531 "@Module", 532 "abstract class BadModule {", 533 " @Multibinds", 534 " @BindsOptionalOf", 535 " abstract Set<String> strings();", 536 "}"); 537 Source badComponent = 538 CompilerTests.javaSource( 539 "test.BadComponent", 540 "package test;", 541 "", 542 "import dagger.Component;", 543 "import java.util.Optional;", 544 "import java.util.Set;", 545 "", 546 "@Component(modules = BadModule.class)", 547 "interface BadComponent {", 548 " Set<String> strings();", 549 " Optional<Set<String>> optionalStrings();", 550 "}"); 551 552 CompilerTests.daggerCompiler(badModule, badComponent) 553 .withProcessingOptions(compilerMode.processorOptions()) 554 .compile( 555 subject -> { 556 subject.hasErrorCount(2); 557 subject.hasErrorContaining( 558 "strings is annotated with more than one of (dagger.Provides, " 559 + "dagger.producers.Produces, dagger.Binds, " 560 + "dagger.multibindings.Multibinds, dagger.BindsOptionalOf)") 561 .onSource(badModule) 562 .onLine(12); 563 subject.hasErrorContaining("test.BadModule has errors") 564 .onSource(badComponent) 565 .onLine(7); 566 }); 567 } 568 } 569