1 /* 2 * Copyright (C) 2018 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 static com.google.testing.compile.CompilationSubject.assertThat; 20 import static dagger.internal.codegen.Compilers.compilerWithOptions; 21 import static dagger.internal.codegen.TestUtils.endsWithMessage; 22 23 import androidx.room.compiler.processing.util.Source; 24 import com.google.common.collect.ImmutableList; 25 import com.google.testing.compile.Compilation; 26 import dagger.testing.compile.CompilerTests; 27 import java.util.regex.Pattern; 28 import org.junit.Test; 29 import org.junit.runner.RunWith; 30 import org.junit.runners.Parameterized; 31 import org.junit.runners.Parameterized.Parameters; 32 33 @RunWith(Parameterized.class) 34 public class DependencyCycleValidationTest { 35 @Parameters(name = "{0}") parameters()36 public static ImmutableList<Object[]> parameters() { 37 return CompilerMode.TEST_PARAMETERS; 38 } 39 40 private final CompilerMode compilerMode; 41 DependencyCycleValidationTest(CompilerMode compilerMode)42 public DependencyCycleValidationTest(CompilerMode compilerMode) { 43 this.compilerMode = compilerMode; 44 } 45 46 private static final Source SIMPLE_CYCLIC_DEPENDENCY = 47 CompilerTests.javaSource( 48 "test.Outer", 49 "package test;", 50 "", 51 "import dagger.Binds;", 52 "import dagger.Component;", 53 "import dagger.Module;", 54 "import dagger.Provides;", 55 "import javax.inject.Inject;", 56 "", 57 "final class Outer {", 58 " static class A {", 59 " @Inject A(C cParam) {}", 60 " }", 61 "", 62 " static class B {", 63 " @Inject B(A aParam) {}", 64 " }", 65 "", 66 " static class C {", 67 " @Inject C(B bParam) {}", 68 " }", 69 "", 70 " @Module", 71 " interface MModule {", 72 " @Binds Object object(C c);", 73 " }", 74 "", 75 " @Component", 76 " interface CComponent {", 77 " C getC();", 78 " }", 79 "}"); 80 81 @Test cyclicDependency()82 public void cyclicDependency() { 83 CompilerTests.daggerCompiler(SIMPLE_CYCLIC_DEPENDENCY) 84 .withProcessingOptions(compilerMode.processorOptions()) 85 .compile( 86 subject -> { 87 subject.hasErrorCount(1); 88 subject.hasErrorContaining( 89 String.join( 90 "\n", 91 "Found a dependency cycle:", 92 " Outer.C is injected at", 93 " Outer.A(cParam)", 94 " Outer.A is injected at", 95 " Outer.B(aParam)", 96 " Outer.B is injected at", 97 " Outer.C(bParam)", 98 " Outer.C is injected at", 99 " Outer.A(cParam)", 100 " ...", 101 "", 102 "The cycle is requested via:", 103 " Outer.C is requested at", 104 " Outer.CComponent.getC()")) 105 .onSource(SIMPLE_CYCLIC_DEPENDENCY) 106 .onLineContaining("interface CComponent"); 107 }); 108 } 109 110 // TODO(b/243720787): Requires CompilationResultSubject#hasErrorContainingMatch() 111 @Test cyclicDependencyWithModuleBindingValidation()112 public void cyclicDependencyWithModuleBindingValidation() { 113 // Cycle errors should not show a dependency trace to an entry point when doing full binding 114 // graph validation. So ensure that the message doesn't end with "test.Outer.C is requested at 115 // test.Outer.CComponent.getC()", as the previous test's message does. 116 Pattern moduleBindingValidationError = 117 endsWithMessage( 118 "Found a dependency cycle:", 119 " Outer.C is injected at", 120 " Outer.A(cParam)", 121 " Outer.A is injected at", 122 " Outer.B(aParam)", 123 " Outer.B is injected at", 124 " Outer.C(bParam)", 125 " Outer.C is injected at", 126 " Outer.A(cParam)", 127 " ...", 128 "", 129 "======================", 130 "Full classname legend:", 131 "======================", 132 "Outer: test.Outer", 133 "========================", 134 "End of classname legend:", 135 "========================"); 136 137 Compilation compilation = 138 compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") 139 .compile(SIMPLE_CYCLIC_DEPENDENCY.toJFO()); 140 assertThat(compilation).failed(); 141 142 assertThat(compilation) 143 .hadErrorContainingMatch(moduleBindingValidationError) 144 .inFile(SIMPLE_CYCLIC_DEPENDENCY.toJFO()) 145 .onLineContaining("interface MModule"); 146 147 assertThat(compilation) 148 .hadErrorContainingMatch(moduleBindingValidationError) 149 .inFile(SIMPLE_CYCLIC_DEPENDENCY.toJFO()) 150 .onLineContaining("interface CComponent"); 151 152 assertThat(compilation).hadErrorCount(2); 153 } 154 cyclicDependencyNotIncludingEntryPoint()155 @Test public void cyclicDependencyNotIncludingEntryPoint() { 156 Source component = 157 CompilerTests.javaSource( 158 "test.Outer", 159 "package test;", 160 "", 161 "import dagger.Component;", 162 "import dagger.Module;", 163 "import dagger.Provides;", 164 "import javax.inject.Inject;", 165 "", 166 "final class Outer {", 167 " static class A {", 168 " @Inject A(C cParam) {}", 169 " }", 170 "", 171 " static class B {", 172 " @Inject B(A aParam) {}", 173 " }", 174 "", 175 " static class C {", 176 " @Inject C(B bParam) {}", 177 " }", 178 "", 179 " static class D {", 180 " @Inject D(C cParam) {}", 181 " }", 182 "", 183 " @Component", 184 " interface DComponent {", 185 " D getD();", 186 " }", 187 "}"); 188 189 CompilerTests.daggerCompiler(component) 190 .withProcessingOptions(compilerMode.processorOptions()) 191 .compile( 192 subject -> { 193 subject.hasErrorCount(1); 194 subject.hasErrorContaining( 195 String.join( 196 "\n", 197 "Found a dependency cycle:", 198 " Outer.C is injected at", 199 " Outer.A(cParam)", 200 " Outer.A is injected at", 201 " Outer.B(aParam)", 202 " Outer.B is injected at", 203 " Outer.C(bParam)", 204 " Outer.C is injected at", 205 " Outer.A(cParam)", 206 " ...", 207 "", 208 "The cycle is requested via:", 209 " Outer.C is injected at", 210 " Outer.D(cParam)", 211 " Outer.D is requested at", 212 " Outer.DComponent.getD()")) 213 .onSource(component) 214 .onLineContaining("interface DComponent"); 215 }); 216 } 217 218 @Test cyclicDependencyNotBrokenByMapBinding()219 public void cyclicDependencyNotBrokenByMapBinding() { 220 Source component = 221 CompilerTests.javaSource( 222 "test.Outer", 223 "package test;", 224 "", 225 "import dagger.Component;", 226 "import dagger.Module;", 227 "import dagger.Provides;", 228 "import dagger.multibindings.IntoMap;", 229 "import dagger.multibindings.StringKey;", 230 "import java.util.Map;", 231 "import javax.inject.Inject;", 232 "", 233 "final class Outer {", 234 " static class A {", 235 " @Inject A(Map<String, C> cMap) {}", 236 " }", 237 "", 238 " static class B {", 239 " @Inject B(A aParam) {}", 240 " }", 241 "", 242 " static class C {", 243 " @Inject C(B bParam) {}", 244 " }", 245 "", 246 " @Component(modules = CModule.class)", 247 " interface CComponent {", 248 " C getC();", 249 " }", 250 "", 251 " @Module", 252 " static class CModule {", 253 " @Provides @IntoMap", 254 " @StringKey(\"C\")", 255 " static C c(C c) {", 256 " return c;", 257 " }", 258 " }", 259 "}"); 260 261 CompilerTests.daggerCompiler(component) 262 .withProcessingOptions(compilerMode.processorOptions()) 263 .compile( 264 subject -> { 265 subject.hasErrorCount(1); 266 subject.hasErrorContaining( 267 String.join( 268 "\n", 269 "Found a dependency cycle:", 270 " Outer.C is injected at", 271 " Outer.CModule.c(c)", 272 " Map<String,Outer.C> is injected at", 273 " Outer.A(cMap)", 274 " Outer.A is injected at", 275 " Outer.B(aParam)", 276 " Outer.B is injected at", 277 " Outer.C(bParam)", 278 " Outer.C is injected at", 279 " Outer.CModule.c(c)", 280 " ...", 281 "", 282 "The cycle is requested via:", 283 " Outer.C is requested at", 284 " Outer.CComponent.getC()")) 285 .onSource(component) 286 .onLineContaining("interface CComponent"); 287 }); 288 } 289 290 @Test cyclicDependencyWithSetBinding()291 public void cyclicDependencyWithSetBinding() { 292 Source component = 293 CompilerTests.javaSource( 294 "test.Outer", 295 "package test;", 296 "", 297 "import dagger.Component;", 298 "import dagger.Module;", 299 "import dagger.Provides;", 300 "import dagger.multibindings.IntoSet;", 301 "import java.util.Set;", 302 "import javax.inject.Inject;", 303 "", 304 "final class Outer {", 305 " static class A {", 306 " @Inject A(Set<C> cSet) {}", 307 " }", 308 "", 309 " static class B {", 310 " @Inject B(A aParam) {}", 311 " }", 312 "", 313 " static class C {", 314 " @Inject C(B bParam) {}", 315 " }", 316 "", 317 " @Component(modules = CModule.class)", 318 " interface CComponent {", 319 " C getC();", 320 " }", 321 "", 322 " @Module", 323 " static class CModule {", 324 " @Provides @IntoSet", 325 " static C c(C c) {", 326 " return c;", 327 " }", 328 " }", 329 "}"); 330 331 CompilerTests.daggerCompiler(component) 332 .withProcessingOptions(compilerMode.processorOptions()) 333 .compile( 334 subject -> { 335 subject.hasErrorCount(1); 336 subject.hasErrorContaining( 337 String.join( 338 "\n", 339 "Found a dependency cycle:", 340 " Outer.C is injected at", 341 " Outer.CModule.c(c)", 342 " Set<Outer.C> is injected at", 343 " Outer.A(cSet)", 344 " Outer.A is injected at", 345 " Outer.B(aParam)", 346 " Outer.B is injected at", 347 " Outer.C(bParam)", 348 " Outer.C is injected at", 349 " Outer.CModule.c(c)", 350 " ...", 351 "", 352 "The cycle is requested via:", 353 " Outer.C is requested at", 354 " Outer.CComponent.getC()")) 355 .onSource(component) 356 .onLineContaining("interface CComponent"); 357 }); 358 } 359 360 @Test falsePositiveCyclicDependencyIndirectionDetected()361 public void falsePositiveCyclicDependencyIndirectionDetected() { 362 Source component = 363 CompilerTests.javaSource( 364 "test.Outer", 365 "package test;", 366 "", 367 "import dagger.Component;", 368 "import dagger.Module;", 369 "import dagger.Provides;", 370 "import javax.inject.Inject;", 371 "import javax.inject.Provider;", 372 "", 373 "final class Outer {", 374 " static class A {", 375 " @Inject A(C cParam) {}", 376 " }", 377 "", 378 " static class B {", 379 " @Inject B(A aParam) {}", 380 " }", 381 "", 382 " static class C {", 383 " @Inject C(B bParam) {}", 384 " }", 385 "", 386 " static class D {", 387 " @Inject D(Provider<C> cParam) {}", 388 " }", 389 "", 390 " @Component", 391 " interface DComponent {", 392 " D getD();", 393 " }", 394 "}"); 395 396 CompilerTests.daggerCompiler(component) 397 .withProcessingOptions(compilerMode.processorOptions()) 398 .compile( 399 subject -> { 400 subject.hasErrorCount(1); 401 subject.hasErrorContaining( 402 String.join( 403 "\n", 404 "Found a dependency cycle:", 405 " Outer.C is injected at", 406 " Outer.A(cParam)", 407 " Outer.A is injected at", 408 " Outer.B(aParam)", 409 " Outer.B is injected at", 410 " Outer.C(bParam)", 411 " Outer.C is injected at", 412 " Outer.A(cParam)", 413 " ...", 414 "", 415 "The cycle is requested via:", 416 " Provider<Outer.C> is injected at", 417 " Outer.D(cParam)", 418 " Outer.D is requested at", 419 " Outer.DComponent.getD()")) 420 .onSource(component) 421 .onLineContaining("interface DComponent"); 422 }); 423 } 424 425 @Test cyclicDependencyInSubcomponents()426 public void cyclicDependencyInSubcomponents() { 427 Source parent = 428 CompilerTests.javaSource( 429 "test.Parent", 430 "package test;", 431 "", 432 "import dagger.Component;", 433 "", 434 "@Component", 435 "interface Parent {", 436 " Child.Builder child();", 437 "}"); 438 Source child = 439 CompilerTests.javaSource( 440 "test.Child", 441 "package test;", 442 "", 443 "import dagger.Subcomponent;", 444 "", 445 "@Subcomponent(modules = CycleModule.class)", 446 "interface Child {", 447 " Grandchild.Builder grandchild();", 448 "", 449 " @Subcomponent.Builder", 450 " interface Builder {", 451 " Child build();", 452 " }", 453 "}"); 454 Source grandchild = 455 CompilerTests.javaSource( 456 "test.Grandchild", 457 "package test;", 458 "", 459 "import dagger.Subcomponent;", 460 "", 461 "@Subcomponent", 462 "interface Grandchild {", 463 " String entry();", 464 "", 465 " @Subcomponent.Builder", 466 " interface Builder {", 467 " Grandchild build();", 468 " }", 469 "}"); 470 Source cycleModule = 471 CompilerTests.javaSource( 472 "test.CycleModule", 473 "package test;", 474 "", 475 "import dagger.Module;", 476 "import dagger.Provides;", 477 "", 478 "@Module", 479 "abstract class CycleModule {", 480 " @Provides static Object object(String string) {", 481 " return string;", 482 " }", 483 "", 484 " @Provides static String string(Object object) {", 485 " return object.toString();", 486 " }", 487 "}"); 488 489 CompilerTests.daggerCompiler(parent, child, grandchild, cycleModule) 490 .withProcessingOptions(compilerMode.processorOptions()) 491 .compile( 492 subject -> { 493 subject.hasErrorCount(1); 494 subject.hasErrorContaining( 495 String.join( 496 "\n", 497 "Found a dependency cycle:", 498 " String is injected at", 499 " CycleModule.object(string)", 500 " Object is injected at", 501 " CycleModule.string(object)", 502 " String is injected at", 503 " CycleModule.object(string)", 504 " ...", 505 "", 506 "The cycle is requested via:", 507 " String is requested at", 508 " Grandchild.entry()")) 509 .onSource(parent) 510 .onLineContaining("interface Parent"); 511 }); 512 } 513 514 @Test cyclicDependencyInSubcomponentsWithChildren()515 public void cyclicDependencyInSubcomponentsWithChildren() { 516 Source parent = 517 CompilerTests.javaSource( 518 "test.Parent", 519 "package test;", 520 "", 521 "import dagger.Component;", 522 "", 523 "@Component", 524 "interface Parent {", 525 " Child.Builder child();", 526 "}"); 527 Source child = 528 CompilerTests.javaSource( 529 "test.Child", 530 "package test;", 531 "", 532 "import dagger.Subcomponent;", 533 "", 534 "@Subcomponent(modules = CycleModule.class)", 535 "interface Child {", 536 " String entry();", 537 "", 538 " Grandchild.Builder grandchild();", 539 "", 540 " @Subcomponent.Builder", 541 " interface Builder {", 542 " Child build();", 543 " }", 544 "}"); 545 // Grandchild has no entry point that depends on the cycle. http://b/111317986 546 Source grandchild = 547 CompilerTests.javaSource( 548 "test.Grandchild", 549 "package test;", 550 "", 551 "import dagger.Subcomponent;", 552 "", 553 "@Subcomponent", 554 "interface Grandchild {", 555 "", 556 " @Subcomponent.Builder", 557 " interface Builder {", 558 " Grandchild build();", 559 " }", 560 "}"); 561 Source cycleModule = 562 CompilerTests.javaSource( 563 "test.CycleModule", 564 "package test;", 565 "", 566 "import dagger.Module;", 567 "import dagger.Provides;", 568 "", 569 "@Module", 570 "abstract class CycleModule {", 571 " @Provides static Object object(String string) {", 572 " return string;", 573 " }", 574 "", 575 " @Provides static String string(Object object) {", 576 " return object.toString();", 577 " }", 578 "}"); 579 580 CompilerTests.daggerCompiler(parent, child, grandchild, cycleModule) 581 .withProcessingOptions(compilerMode.processorOptions()) 582 .compile( 583 subject -> { 584 subject.hasErrorCount(1); 585 subject.hasErrorContaining( 586 String.join( 587 "\n", 588 "Found a dependency cycle:", 589 " String is injected at", 590 " CycleModule.object(string)", 591 " Object is injected at", 592 " CycleModule.string(object)", 593 " String is injected at", 594 " CycleModule.object(string)", 595 " ...", 596 "", 597 "The cycle is requested via:", 598 " String is requested at", 599 " Child.entry() [Parent → Child]")) 600 .onSource(parent) 601 .onLineContaining("interface Parent"); 602 }); 603 } 604 605 @Test circularBindsMethods()606 public void circularBindsMethods() { 607 Source qualifier = 608 CompilerTests.javaSource( 609 "test.SomeQualifier", 610 "package test;", 611 "", 612 "import javax.inject.Qualifier;", 613 "", 614 "@Qualifier @interface SomeQualifier {}"); 615 Source module = 616 CompilerTests.javaSource( 617 "test.TestModule", 618 "package test;", 619 "", 620 "import dagger.Binds;", 621 "import dagger.Module;", 622 "", 623 "@Module", 624 "abstract class TestModule {", 625 " @Binds abstract Object bindUnqualified(@SomeQualifier Object qualified);", 626 " @Binds @SomeQualifier abstract Object bindQualified(Object unqualified);", 627 "}"); 628 Source component = 629 CompilerTests.javaSource( 630 "test.TestComponent", 631 "package test;", 632 "", 633 "import dagger.Component;", 634 "", 635 "@Component(modules = TestModule.class)", 636 "interface TestComponent {", 637 " Object unqualified();", 638 "}"); 639 640 CompilerTests.daggerCompiler(qualifier, module, component) 641 .withProcessingOptions(compilerMode.processorOptions()) 642 .compile( 643 subject -> { 644 subject.hasErrorCount(1); 645 subject.hasErrorContaining( 646 String.join( 647 "\n", 648 "Found a dependency cycle:", 649 " Object is injected at", 650 " TestModule.bindQualified(unqualified)", 651 " @SomeQualifier Object is injected at", 652 " TestModule.bindUnqualified(qualified)", 653 " Object is injected at", 654 " TestModule.bindQualified(unqualified)", 655 " ...", 656 "", 657 "The cycle is requested via:", 658 " Object is requested at", 659 " TestComponent.unqualified()")) 660 .onSource(component) 661 .onLineContaining("interface TestComponent"); 662 }); 663 } 664 665 @Test selfReferentialBinds()666 public void selfReferentialBinds() { 667 Source module = 668 CompilerTests.javaSource( 669 "test.TestModule", 670 "package test;", 671 "", 672 "import dagger.Binds;", 673 "import dagger.Module;", 674 "", 675 "@Module", 676 "abstract class TestModule {", 677 " @Binds abstract Object bindToSelf(Object sameKey);", 678 "}"); 679 Source component = 680 CompilerTests.javaSource( 681 "test.TestComponent", 682 "package test;", 683 "", 684 "import dagger.Component;", 685 "", 686 "@Component(modules = TestModule.class)", 687 "interface TestComponent {", 688 " Object selfReferential();", 689 "}"); 690 691 CompilerTests.daggerCompiler(module, component) 692 .withProcessingOptions(compilerMode.processorOptions()) 693 .compile( 694 subject -> { 695 subject.hasErrorCount(1); 696 subject.hasErrorContaining( 697 String.join( 698 "\n", 699 "Found a dependency cycle:", 700 " Object is injected at", 701 " TestModule.bindToSelf(sameKey)", 702 " Object is injected at", 703 " TestModule.bindToSelf(sameKey)", 704 " ...", 705 "", 706 "The cycle is requested via:", 707 " Object is requested at", 708 " TestComponent.selfReferential()")) 709 .onSource(component) 710 .onLineContaining("interface TestComponent"); 711 }); 712 } 713 714 @Test cycleFromMembersInjectionMethod_WithSameKeyAsMembersInjectionMethod()715 public void cycleFromMembersInjectionMethod_WithSameKeyAsMembersInjectionMethod() { 716 Source a = 717 CompilerTests.javaSource( 718 "test.A", 719 "package test;", 720 "", 721 "import javax.inject.Inject;", 722 "", 723 "class A {", 724 " @Inject A() {}", 725 " @Inject B b;", 726 "}"); 727 Source b = 728 CompilerTests.javaSource( 729 "test.B", 730 "package test;", 731 "", 732 "import javax.inject.Inject;", 733 "", 734 "class B {", 735 " @Inject B() {}", 736 " @Inject A a;", 737 "}"); 738 Source component = 739 CompilerTests.javaSource( 740 "test.CycleComponent", 741 "package test;", 742 "", 743 "import dagger.Component;", 744 "", 745 "@Component", 746 "interface CycleComponent {", 747 " void inject(A a);", 748 "}"); 749 750 CompilerTests.daggerCompiler(a, b, component) 751 .withProcessingOptions(compilerMode.processorOptions()) 752 .compile( 753 subject -> { 754 subject.hasErrorCount(1); 755 subject.hasErrorContaining( 756 String.join( 757 "\n", 758 "Found a dependency cycle:", 759 " test.B is injected at", 760 " test.A.b", 761 " test.A is injected at", 762 " test.B.a", 763 " test.B is injected at", 764 " test.A.b", 765 " ...", 766 "", 767 "The cycle is requested via:", 768 " test.B is injected at", 769 " test.A.b", 770 " test.A is injected at", 771 " CycleComponent.inject(test.A)")) 772 .onSource(component) 773 .onLineContaining("interface CycleComponent"); 774 }); 775 } 776 777 @Test longCycleMaskedByShortBrokenCycles()778 public void longCycleMaskedByShortBrokenCycles() { 779 Source cycles = 780 CompilerTests.javaSource( 781 "test.Cycles", 782 "package test;", 783 "", 784 "import javax.inject.Inject;", 785 "import javax.inject.Provider;", 786 "import dagger.Component;", 787 "", 788 "final class Cycles {", 789 " static class A {", 790 " @Inject A(Provider<A> aProvider, B b) {}", 791 " }", 792 "", 793 " static class B {", 794 " @Inject B(Provider<B> bProvider, A a) {}", 795 " }", 796 "", 797 " @Component", 798 " interface C {", 799 " A a();", 800 " }", 801 "}"); 802 CompilerTests.daggerCompiler(cycles) 803 .withProcessingOptions(compilerMode.processorOptions()) 804 .compile( 805 subject -> { 806 subject.hasErrorCount(1); 807 subject.hasErrorContaining("Found a dependency cycle:") 808 .onSource(cycles) 809 .onLineContaining("interface C"); 810 }); 811 } 812 } 813