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