xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/DependencyCycleValidationTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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