xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/ScopingValidationTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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.XProcessingEnv;
20 import androidx.room.compiler.processing.util.Source;
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.JUnit4;
26 
27 @RunWith(JUnit4.class)
28 public class ScopingValidationTest {
29   @Test
componentWithoutScopeIncludesScopedBindings_Fail()30   public void componentWithoutScopeIncludesScopedBindings_Fail() {
31     Source componentFile =
32         CompilerTests.javaSource(
33             "test.MyComponent",
34             "package test;",
35             "",
36             "import dagger.Component;",
37             "import javax.inject.Singleton;",
38             "",
39             "@Component(modules = ScopedModule.class)",
40             "interface MyComponent {",
41             "  ScopedType string();",
42             "}");
43     Source typeFile =
44         CompilerTests.javaSource(
45             "test.ScopedType",
46             "package test;",
47             "",
48             "import javax.inject.Inject;",
49             "import javax.inject.Singleton;",
50             "",
51             "@Singleton",
52             "class ScopedType {",
53             "  @Inject ScopedType(String s, long l, float f) {}",
54             "}");
55     Source moduleFile =
56         CompilerTests.javaSource(
57             "test.ScopedModule",
58             "package test;",
59             "",
60             "import dagger.Module;",
61             "import dagger.Provides;",
62             "import javax.inject.Singleton;",
63             "",
64             "@Module",
65             "class ScopedModule {",
66             "  @Provides @Singleton String string() { return \"a string\"; }",
67             "  @Provides long integer() { return 0L; }",
68             "  @Provides float floatingPoint() { return 0.0f; }",
69             "}");
70 
71     CompilerTests.daggerCompiler(componentFile, typeFile, moduleFile)
72         .compile(
73             subject -> {
74               subject.hasErrorCount(1);
75               subject.hasErrorContaining(
76                   String.join(
77                       "\n",
78                       "MyComponent (unscoped) may not reference scoped bindings:",
79                       "    @Singleton class ScopedType",
80                       "    ScopedType is requested at",
81                       "        MyComponent.string()",
82                       "",
83                       "    @Provides @Singleton String ScopedModule.string()"));
84             });
85   }
86 
87   @Test // b/79859714
bindsWithChildScope_inParentModule_notAllowed()88   public void bindsWithChildScope_inParentModule_notAllowed() {
89     Source childScope =
90         CompilerTests.javaSource(
91             "test.ChildScope",
92             "package test;",
93             "",
94             "import javax.inject.Scope;",
95             "",
96             "@Scope",
97             "@interface ChildScope {}");
98 
99     Source foo =
100         CompilerTests.javaSource(
101             "test.Foo",
102             "package test;",
103             "", //
104             "interface Foo {}");
105 
106     Source fooImpl =
107         CompilerTests.javaSource(
108             "test.FooImpl",
109             "package test;",
110             "",
111             "import javax.inject.Inject;",
112             "",
113             "class FooImpl implements Foo {",
114             "  @Inject FooImpl() {}",
115             "}");
116 
117     Source parentModule =
118         CompilerTests.javaSource(
119             "test.ParentModule",
120             "package test;",
121             "",
122             "import dagger.Binds;",
123             "import dagger.Module;",
124             "",
125             "@Module",
126             "interface ParentModule {",
127             "  @Binds @ChildScope Foo bind(FooImpl fooImpl);",
128             "}");
129 
130     Source parent =
131         CompilerTests.javaSource(
132             "test.Parent",
133             "package test;",
134             "",
135             "import dagger.Component;",
136             "import javax.inject.Singleton;",
137             "",
138             "@Singleton",
139             "@Component(modules = ParentModule.class)",
140             "interface Parent {",
141             "  Child child();",
142             "}");
143 
144     Source child =
145         CompilerTests.javaSource(
146             "test.Child",
147             "package test;",
148             "",
149             "import dagger.Subcomponent;",
150             "",
151             "@ChildScope",
152             "@Subcomponent",
153             "interface Child {",
154             "  Foo foo();",
155             "}");
156 
157     CompilerTests.daggerCompiler(childScope, foo, fooImpl, parentModule, parent, child)
158         .compile(
159             subject -> {
160               subject.hasErrorCount(1);
161               subject.hasErrorContaining(
162                   String.join(
163                       "\n",
164                       "Parent scoped with @Singleton may not reference bindings with different "
165                           + "scopes:",
166                       "    @Binds @ChildScope Foo ParentModule.bind(FooImpl)"));
167             });
168   }
169 
170   @Test
componentWithScopeIncludesIncompatiblyScopedBindings_Fail()171   public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() {
172     Source componentFile =
173         CompilerTests.javaSource(
174             "test.MyComponent",
175             "package test;",
176             "",
177             "import dagger.Component;",
178             "import javax.inject.Singleton;",
179             "",
180             "@Singleton",
181             "@Component(modules = ScopedModule.class)",
182             "interface MyComponent {",
183             "  ScopedType string();",
184             "}");
185     Source scopeFile =
186         CompilerTests.javaSource(
187             "test.PerTest",
188             "package test;",
189             "",
190             "import javax.inject.Scope;",
191             "",
192             "@Scope",
193             "@interface PerTest {}");
194     Source scopeWithAttribute =
195         CompilerTests.javaSource(
196             "test.Per",
197             "package test;",
198             "",
199             "import javax.inject.Scope;",
200             "",
201             "@Scope",
202             "@interface Per {",
203             "  Class<?> value();",
204             "}");
205     Source typeFile =
206         CompilerTests.javaSource(
207             "test.ScopedType",
208             "package test;",
209             "",
210             "import javax.inject.Inject;",
211             "",
212             "@PerTest", // incompatible scope
213             "class ScopedType {",
214             "  @Inject ScopedType(String s, long l, float f, boolean b) {}",
215             "}");
216     Source moduleFile =
217         CompilerTests.javaSource(
218             "test.ScopedModule",
219             "package test;",
220             "",
221             "import dagger.Module;",
222             "import dagger.Provides;",
223             "import javax.inject.Singleton;",
224             "",
225             "@Module",
226             "class ScopedModule {",
227             "  @Provides @PerTest String string() { return \"a string\"; }", // incompatible scope
228             "  @Provides long integer() { return 0L; }", // unscoped - valid
229             "  @Provides @Singleton float floatingPoint() { return 0.0f; }", // same scope - valid
230             "  @Provides @Per(MyComponent.class) boolean bool() { return false; }", // incompatible
231             "}");
232 
233     CompilerTests.daggerCompiler(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile)
234         .compile(
235             subject -> {
236               subject.hasErrorCount(1);
237               subject
238                   .hasErrorContaining(
239                       String.join(
240                           "\n",
241                           "MyComponent scoped with @Singleton may not reference bindings with "
242                               + "different scopes:",
243                           "    @PerTest class ScopedType",
244                           "    ScopedType is requested at",
245                           "        MyComponent.string()",
246                           "",
247                           "    @Provides @PerTest String ScopedModule.string()",
248                           "",
249                           // TODO(b/241293838): Remove dependency on backend once this bug is fixed.
250                           CompilerTests.backend(subject).equals(XProcessingEnv.Backend.JAVAC)
251                               ? "    @Provides @Per(MyComponent.class) boolean ScopedModule.bool()"
252                               : "    @Provides @Per(MyComponent) boolean ScopedModule.bool()"))
253                   .onSource(componentFile)
254                   .onLineContaining("interface MyComponent");
255             });
256 
257     // The @Inject binding for ScopedType should not appear here, but the @Singleton binding should.
258     CompilerTests.daggerCompiler(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile)
259         .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR"))
260         .compile(
261             subject -> {
262               subject.hasErrorCount(2);
263               subject.hasErrorContaining(
264                       String.join(
265                           "\n",
266                           "ScopedModule contains bindings with different scopes:",
267                           "    @Provides @PerTest String ScopedModule.string()",
268                           "",
269                           "    @Provides @Singleton float ScopedModule.floatingPoint()",
270                           "",
271                           // TODO(b/241293838): Remove dependency on backend once this bug is fixed.
272                           CompilerTests.backend(subject).equals(XProcessingEnv.Backend.JAVAC)
273                               ? "    @Provides @Per(MyComponent.class) boolean ScopedModule.bool()"
274                               : "    @Provides @Per(MyComponent) boolean ScopedModule.bool()"))
275                   .onSource(moduleFile)
276                   .onLineContaining("class ScopedModule");
277             });
278   }
279 
280   @Test
fullBindingGraphValidationDoesNotReportForOneScope()281   public void fullBindingGraphValidationDoesNotReportForOneScope() {
282     CompilerTests.daggerCompiler(
283             CompilerTests.javaSource(
284                 "test.TestModule",
285                 "package test;",
286                 "",
287                 "import dagger.Module;",
288                 "import dagger.Provides;",
289                 "import javax.inject.Singleton;",
290                 "",
291                 "@Module",
292                 "interface TestModule {",
293                 "  @Provides @Singleton static Object object() { return \"object\"; }",
294                 "  @Provides @Singleton static String string() { return \"string\"; }",
295                 "  @Provides static int integer() { return 4; }",
296                 "}"))
297         .withProcessingOptions(
298             ImmutableMap.<String, String>builder()
299                 .put("dagger.fullBindingGraphValidation", "ERROR")
300                 .put("dagger.moduleHasDifferentScopesValidation", "ERROR")
301                 .buildOrThrow())
302         .compile(
303             subject -> {
304               subject.hasErrorCount(0);
305               subject.hasWarningCount(0);
306             });
307   }
308 
309   @Test
fullBindingGraphValidationDoesNotReportInjectBindings()310   public void fullBindingGraphValidationDoesNotReportInjectBindings() {
311     CompilerTests.daggerCompiler(
312             CompilerTests.javaSource(
313                 "test.UsedInRootRedScoped",
314                 "package test;",
315                 "",
316                 "import javax.inject.Inject;",
317                 "",
318                 "@RedScope",
319                 "final class UsedInRootRedScoped {",
320                 "  @Inject UsedInRootRedScoped() {}",
321                 "}"),
322             CompilerTests.javaSource(
323                 "test.UsedInRootBlueScoped",
324                 "package test;",
325                 "",
326                 "import javax.inject.Inject;",
327                 "",
328                 "@BlueScope",
329                 "final class UsedInRootBlueScoped {",
330                 "  @Inject UsedInRootBlueScoped() {}",
331                 "}"),
332             CompilerTests.javaSource(
333                 "test.RedScope",
334                 "package test;",
335                 "",
336                 "import javax.inject.Scope;",
337                 "",
338                 "@Scope",
339                 "@interface RedScope {}"),
340             CompilerTests.javaSource(
341                 "test.BlueScope",
342                 "package test;",
343                 "",
344                 "import javax.inject.Scope;",
345                 "",
346                 "@Scope",
347                 "@interface BlueScope {}"),
348             CompilerTests.javaSource(
349                 "test.TestModule",
350                 "package test;",
351                 "",
352                 "import dagger.Module;",
353                 "import dagger.Provides;",
354                 "import javax.inject.Singleton;",
355                 "",
356                 "@Module(subcomponents = Child.class)",
357                 "interface TestModule {",
358                 "  @Provides @Singleton",
359                 "  static Object object(",
360                 "      UsedInRootRedScoped usedInRootRedScoped,",
361                 "      UsedInRootBlueScoped usedInRootBlueScoped) {",
362                 "    return \"object\";",
363                 "  }",
364                 "}"),
365             CompilerTests.javaSource(
366                 "test.Child",
367                 "package test;",
368                 "",
369                 "import dagger.Subcomponent;",
370                 "",
371                 "@Subcomponent",
372                 "interface Child {",
373                 "  UsedInChildRedScoped usedInChildRedScoped();",
374                 "  UsedInChildBlueScoped usedInChildBlueScoped();",
375                 "",
376                 "  @Subcomponent.Builder",
377                 "  interface Builder {",
378                 "    Child child();",
379                 "  }",
380                 "}"),
381             CompilerTests.javaSource(
382                 "test.UsedInChildRedScoped",
383                 "package test;",
384                 "",
385                 "import javax.inject.Inject;",
386                 "",
387                 "@RedScope",
388                 "final class UsedInChildRedScoped {",
389                 "  @Inject UsedInChildRedScoped() {}",
390                 "}"),
391             CompilerTests.javaSource(
392                 "test.UsedInChildBlueScoped",
393                 "package test;",
394                 "",
395                 "import javax.inject.Inject;",
396                 "",
397                 "@BlueScope",
398                 "final class UsedInChildBlueScoped {",
399                 "  @Inject UsedInChildBlueScoped() {}",
400                 "}"))
401         .withProcessingOptions(
402             ImmutableMap.<String, String>builder()
403                 .put("dagger.fullBindingGraphValidation", "ERROR")
404                 .put("dagger.moduleHasDifferentScopesValidation", "ERROR")
405                 .buildOrThrow())
406         .compile(
407             subject -> {
408               subject.hasErrorCount(0);
409               subject.hasWarningCount(0);
410             });
411   }
412 
413   @Test
componentWithScopeCanDependOnMultipleScopedComponents()414   public void componentWithScopeCanDependOnMultipleScopedComponents() {
415     // If a scoped component will have dependencies, they can include multiple scoped component
416     Source type =
417         CompilerTests.javaSource(
418             "test.SimpleType",
419             "package test;",
420             "",
421             "import javax.inject.Inject;",
422             "",
423             "class SimpleType {",
424             "  @Inject SimpleType() {}",
425             "  static class A { @Inject A() {} }",
426             "  static class B { @Inject B() {} }",
427             "}");
428     Source simpleScope =
429         CompilerTests.javaSource(
430             "test.SimpleScope",
431             "package test;",
432             "",
433             "import javax.inject.Scope;",
434             "",
435             "@Scope @interface SimpleScope {}");
436     Source singletonScopedA =
437         CompilerTests.javaSource(
438             "test.SingletonComponentA",
439             "package test;",
440             "",
441             "import dagger.Component;",
442             "import javax.inject.Singleton;",
443             "",
444             "@Singleton",
445             "@Component",
446             "interface SingletonComponentA {",
447             "  SimpleType.A type();",
448             "}");
449     Source singletonScopedB =
450         CompilerTests.javaSource(
451             "test.SingletonComponentB",
452             "package test;",
453             "",
454             "import dagger.Component;",
455             "import javax.inject.Singleton;",
456             "",
457             "@Singleton",
458             "@Component",
459             "interface SingletonComponentB {",
460             "  SimpleType.B type();",
461             "}");
462     Source scopeless =
463         CompilerTests.javaSource(
464             "test.ScopelessComponent",
465             "package test;",
466             "",
467             "import dagger.Component;",
468             "",
469             "@Component",
470             "interface ScopelessComponent {",
471             "  SimpleType type();",
472             "}");
473     Source simpleScoped =
474         CompilerTests.javaSource(
475             "test.SimpleScopedComponent",
476             "package test;",
477             "",
478             "import dagger.Component;",
479             "",
480             "@SimpleScope",
481             "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})",
482             "interface SimpleScopedComponent {",
483             "  SimpleType.A type();",
484             "}");
485 
486     CompilerTests.daggerCompiler(
487             type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless)
488         .compile(
489             subject -> {
490               subject.hasErrorCount(0);
491               subject.hasWarningCount(0);
492             });
493   }
494 
495 
496 
497   // Tests the following component hierarchy:
498   //
499   //        @ScopeA
500   //        ComponentA
501   //        [SimpleType getSimpleType()]
502   //        /        \
503   //       /          \
504   //   @ScopeB         @ScopeB
505   //   ComponentB1     ComponentB2
506   //      \            [SimpleType getSimpleType()]
507   //       \          /
508   //        \        /
509   //         @ScopeC
510   //         ComponentC
511   //         [SimpleType getSimpleType()]
512   @Test
componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond()513   public void componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond() {
514     Source type =
515         CompilerTests.javaSource(
516             "test.SimpleType",
517             "package test;",
518             "",
519             "import javax.inject.Inject;",
520             "",
521             "class SimpleType {",
522             "  @Inject SimpleType() {}",
523             "}");
524     Source simpleScope =
525         CompilerTests.javaSource(
526             "test.SimpleScope",
527             "package test;",
528             "",
529             "import javax.inject.Scope;",
530             "",
531             "@Scope @interface SimpleScope {}");
532     Source scopeA =
533         CompilerTests.javaSource(
534             "test.ScopeA",
535             "package test;",
536             "",
537             "import javax.inject.Scope;",
538             "",
539             "@Scope @interface ScopeA {}");
540     Source scopeB =
541         CompilerTests.javaSource(
542             "test.ScopeB",
543             "package test;",
544             "",
545             "import javax.inject.Scope;",
546             "",
547             "@Scope @interface ScopeB {}");
548     Source componentA =
549         CompilerTests.javaSource(
550             "test.ComponentA",
551             "package test;",
552             "",
553             "import dagger.Component;",
554             "",
555             "@ScopeA",
556             "@Component",
557             "interface ComponentA {",
558             "  SimpleType type();",
559             "}");
560     Source componentB1 =
561         CompilerTests.javaSource(
562             "test.ComponentB1",
563             "package test;",
564             "",
565             "import dagger.Component;",
566             "",
567             "@ScopeB",
568             "@Component(dependencies = ComponentA.class)",
569             "interface ComponentB1 {",
570             "  SimpleType type();",
571             "}");
572     Source componentB2 =
573         CompilerTests.javaSource(
574             "test.ComponentB2",
575             "package test;",
576             "",
577             "import dagger.Component;",
578             "",
579             "@ScopeB",
580             "@Component(dependencies = ComponentA.class)",
581             "interface ComponentB2 {",
582             "}");
583     Source componentC =
584         CompilerTests.javaSource(
585             "test.ComponentC",
586             "package test;",
587             "",
588             "import dagger.Component;",
589             "",
590             "@SimpleScope",
591             "@Component(dependencies = {ComponentB1.class, ComponentB2.class})",
592             "interface ComponentC {",
593             "  SimpleType type();",
594             "}");
595 
596     CompilerTests.daggerCompiler(
597             type, simpleScope, scopeA, scopeB, componentA, componentB1, componentB2, componentC)
598         .compile(
599             subject -> {
600               subject.hasErrorCount(0);
601               subject.hasWarningCount(0);
602             });
603   }
604 
605   @Test
componentWithoutScopeCannotDependOnScopedComponent()606   public void componentWithoutScopeCannotDependOnScopedComponent() {
607     Source type =
608         CompilerTests.javaSource(
609             "test.SimpleType",
610             "package test;",
611             "",
612             "import javax.inject.Inject;",
613             "",
614             "class SimpleType {",
615             "  @Inject SimpleType() {}",
616             "}");
617     Source scopedComponent =
618         CompilerTests.javaSource(
619             "test.ScopedComponent",
620             "package test;",
621             "",
622             "import dagger.Component;",
623             "import javax.inject.Singleton;",
624             "",
625             "@Singleton",
626             "@Component",
627             "interface ScopedComponent {",
628             "  SimpleType type();",
629             "}");
630     Source unscopedComponent =
631         CompilerTests.javaSource(
632             "test.UnscopedComponent",
633             "package test;",
634             "",
635             "import dagger.Component;",
636             "import javax.inject.Singleton;",
637             "",
638             "@Component(dependencies = ScopedComponent.class)",
639             "interface UnscopedComponent {",
640             "  SimpleType type();",
641             "}");
642 
643     CompilerTests.daggerCompiler(type, scopedComponent, unscopedComponent)
644         .compile(
645             subject -> {
646               subject.hasErrorCount(1);
647               subject.hasErrorContaining(
648                   String.join(
649                       "\n",
650                       "test.UnscopedComponent (unscoped) cannot depend on scoped components:",
651                       "    @Singleton test.ScopedComponent"));
652             });
653   }
654 
655   @Test
componentWithSingletonScopeMayNotDependOnOtherScope()656   public void componentWithSingletonScopeMayNotDependOnOtherScope() {
657     // Singleton must be the widest lifetime of present scopes.
658     Source type =
659         CompilerTests.javaSource(
660             "test.SimpleType",
661             "package test;",
662             "",
663             "import javax.inject.Inject;",
664             "",
665             "class SimpleType {",
666             "  @Inject SimpleType() {}",
667             "}");
668     Source simpleScope =
669         CompilerTests.javaSource(
670             "test.SimpleScope",
671             "package test;",
672             "",
673             "import javax.inject.Scope;",
674             "",
675             "@Scope @interface SimpleScope {}");
676     Source simpleScoped =
677         CompilerTests.javaSource(
678             "test.SimpleScopedComponent",
679             "package test;",
680             "",
681             "import dagger.Component;",
682             "",
683             "@SimpleScope",
684             "@Component",
685             "interface SimpleScopedComponent {",
686             "  SimpleType type();",
687             "}");
688     Source singletonScoped =
689         CompilerTests.javaSource(
690             "test.SingletonComponent",
691             "package test;",
692             "",
693             "import dagger.Component;",
694             "import javax.inject.Singleton;",
695             "",
696             "@Singleton",
697             "@Component(dependencies = SimpleScopedComponent.class)",
698             "interface SingletonComponent {",
699             "  SimpleType type();",
700             "}");
701 
702     CompilerTests.daggerCompiler(type, simpleScope, simpleScoped, singletonScoped)
703         .compile(
704             subject -> {
705               subject.hasErrorCount(1);
706               subject.hasErrorContaining(
707                   String.join(
708                       "\n",
709                       "This @Singleton component cannot depend on scoped components:",
710                       "    @test.SimpleScope test.SimpleScopedComponent"));
711             });
712   }
713 
714   @Test
componentScopeWithMultipleScopedDependenciesMustNotCycle()715   public void componentScopeWithMultipleScopedDependenciesMustNotCycle() {
716     Source type =
717         CompilerTests.javaSource(
718             "test.SimpleType",
719             "package test;",
720             "",
721             "import javax.inject.Inject;",
722             "",
723             "class SimpleType {",
724             "  @Inject SimpleType() {}",
725             "}");
726     Source scopeA =
727         CompilerTests.javaSource(
728             "test.ScopeA",
729             "package test;",
730             "",
731             "import javax.inject.Scope;",
732             "",
733             "@Scope @interface ScopeA {}");
734     Source scopeB =
735         CompilerTests.javaSource(
736             "test.ScopeB",
737             "package test;",
738             "",
739             "import javax.inject.Scope;",
740             "",
741             "@Scope @interface ScopeB {}");
742     Source longLifetime =
743         CompilerTests.javaSource(
744             "test.ComponentLong",
745             "package test;",
746             "",
747             "import dagger.Component;",
748             "",
749             "@ScopeA",
750             "@Component",
751             "interface ComponentLong {",
752             "  SimpleType type();",
753             "}");
754     Source mediumLifetime1 =
755         CompilerTests.javaSource(
756             "test.ComponentMedium1",
757             "package test;",
758             "",
759             "import dagger.Component;",
760             "",
761             "@ScopeB",
762             "@Component(dependencies = ComponentLong.class)",
763             "interface ComponentMedium1 {",
764             "  SimpleType type();",
765             "}");
766     Source mediumLifetime2 =
767         CompilerTests.javaSource(
768             "test.ComponentMedium2",
769             "package test;",
770             "",
771             "import dagger.Component;",
772             "",
773             "@ScopeB",
774             "@Component",
775             "interface ComponentMedium2 {",
776             "}");
777     Source shortLifetime =
778         CompilerTests.javaSource(
779             "test.ComponentShort",
780             "package test;",
781             "",
782             "import dagger.Component;",
783             "",
784             "@ScopeA",
785             "@Component(dependencies = {ComponentMedium1.class, ComponentMedium2.class})",
786             "interface ComponentShort {",
787             "  SimpleType type();",
788             "}");
789 
790     CompilerTests.daggerCompiler(
791             type, scopeA, scopeB, longLifetime, mediumLifetime1, mediumLifetime2, shortLifetime)
792         .compile(
793             subject -> {
794               subject.hasErrorCount(1);
795               subject.hasErrorContaining(
796                   String.join(
797                       "\n",
798                       "test.ComponentShort depends on scoped components in a non-hierarchical "
799                           + "scope ordering:",
800                       "    @test.ScopeA test.ComponentLong",
801                       "    @test.ScopeB test.ComponentMedium1",
802                       "    @test.ScopeA test.ComponentShort"));
803             });
804   }
805 
806   @Test
componentScopeAncestryMustNotCycle()807   public void componentScopeAncestryMustNotCycle() {
808     // The dependency relationship of components is necessarily from shorter lifetimes to
809     // longer lifetimes.  The scoping annotations must reflect this, and so one cannot declare
810     // scopes on components such that they cycle.
811     Source type =
812         CompilerTests.javaSource(
813             "test.SimpleType",
814             "package test;",
815             "",
816             "import javax.inject.Inject;",
817             "",
818             "class SimpleType {",
819             "  @Inject SimpleType() {}",
820             "}");
821     Source scopeA =
822         CompilerTests.javaSource(
823             "test.ScopeA",
824             "package test;",
825             "",
826             "import javax.inject.Scope;",
827             "",
828             "@Scope @interface ScopeA {}");
829     Source scopeB =
830         CompilerTests.javaSource(
831             "test.ScopeB",
832             "package test;",
833             "",
834             "import javax.inject.Scope;",
835             "",
836             "@Scope @interface ScopeB {}");
837     Source longLifetime =
838         CompilerTests.javaSource(
839             "test.ComponentLong",
840             "package test;",
841             "",
842             "import dagger.Component;",
843             "",
844             "@ScopeA",
845             "@Component",
846             "interface ComponentLong {",
847             "  SimpleType type();",
848             "}");
849     Source mediumLifetime =
850         CompilerTests.javaSource(
851             "test.ComponentMedium",
852             "package test;",
853             "",
854             "import dagger.Component;",
855             "",
856             "@ScopeB",
857             "@Component(dependencies = ComponentLong.class)",
858             "interface ComponentMedium {",
859             "  SimpleType type();",
860             "}");
861     Source shortLifetime =
862         CompilerTests.javaSource(
863             "test.ComponentShort",
864             "package test;",
865             "",
866             "import dagger.Component;",
867             "",
868             "@ScopeA",
869             "@Component(dependencies = ComponentMedium.class)",
870             "interface ComponentShort {",
871             "  SimpleType type();",
872             "}");
873 
874     CompilerTests.daggerCompiler(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime)
875         .compile(
876             subject -> {
877               subject.hasErrorCount(1);
878               subject.hasErrorContaining(
879                   String.join(
880                       "\n",
881                       "test.ComponentShort depends on scoped components in a non-hierarchical "
882                           + "scope ordering:",
883                       "    @test.ScopeA test.ComponentLong",
884                       "    @test.ScopeB test.ComponentMedium",
885                       "    @test.ScopeA test.ComponentShort"));
886             });
887 
888     // Test that compilation succeeds when transitive validation is disabled because the scope cycle
889     // cannot be detected.
890     CompilerTests.daggerCompiler(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime)
891         .withProcessingOptions(
892             ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED"))
893         .compile(subject -> subject.hasErrorCount(0));
894   }
895 
896   @Test
reusableNotAllowedOnComponent()897   public void reusableNotAllowedOnComponent() {
898     Source someComponent =
899         CompilerTests.javaSource(
900             "test.SomeComponent",
901             "package test;",
902             "",
903             "import dagger.Component;",
904             "import dagger.Reusable;",
905             "",
906             "@Reusable",
907             "@Component",
908             "interface SomeComponent {}");
909     CompilerTests.daggerCompiler(someComponent)
910         .compile(
911             subject -> {
912               subject.hasErrorCount(1);
913               subject.hasErrorContaining(
914                       "@Reusable cannot be applied to components or subcomponents")
915                   .onSource(someComponent)
916                   .onLine(6);
917             });
918   }
919 
920   @Test
reusableNotAllowedOnSubcomponent()921   public void reusableNotAllowedOnSubcomponent() {
922     Source someSubcomponent =
923         CompilerTests.javaSource(
924             "test.SomeComponent",
925             "package test;",
926             "",
927             "import dagger.Reusable;",
928             "import dagger.Subcomponent;",
929             "",
930             "@Reusable",
931             "@Subcomponent",
932             "interface SomeSubcomponent {}");
933     CompilerTests.daggerCompiler(someSubcomponent)
934         .compile(
935             subject -> {
936               subject.hasErrorCount(1);
937               subject.hasErrorContaining(
938                       "@Reusable cannot be applied to components or subcomponents")
939                   .onSource(someSubcomponent)
940                   .onLine(6);
941             });
942   }
943 }
944