xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/ComponentValidationTest.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 static com.google.testing.compile.CompilationSubject.assertThat;
20 import static dagger.internal.codegen.Compilers.daggerCompiler;
21 import static dagger.internal.codegen.TestUtils.message;
22 
23 import androidx.room.compiler.processing.util.Source;
24 import com.google.common.collect.ImmutableMap;
25 import com.google.testing.compile.Compilation;
26 import com.google.testing.compile.JavaFileObjects;
27 import dagger.testing.compile.CompilerTests;
28 import javax.tools.JavaFileObject;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.junit.runners.JUnit4;
32 
33 @RunWith(JUnit4.class)
34 public final class ComponentValidationTest {
35   @Test
componentOnConcreteClass()36   public void componentOnConcreteClass() {
37     Source componentFile =
38         CompilerTests.javaSource(
39             "test.NotAComponent",
40             "package test;",
41             "",
42             "import dagger.Component;",
43             "",
44             "@Component",
45             "final class NotAComponent {}");
46     CompilerTests.daggerCompiler(componentFile)
47         .compile(
48             subject -> {
49               subject.hasErrorCount(1);
50               subject.hasErrorContaining("interface");
51             });
52   }
53 
54   @Test
componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName()55   public void componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName() {
56     Source componentFile =
57         CompilerTests.javaSource(
58             "test.TestComponent",
59             "package test;",
60             "",
61             "import dagger.Component;",
62             "",
63             "@Component(modules=TestModule.class)",
64             "interface TestComponent {",
65             "  String builder();",
66             "}");
67     Source moduleFile =
68         CompilerTests.javaSource(
69             "test.TestModule",
70             "package test;",
71             "",
72             "import dagger.Module;",
73             "import dagger.Provides;",
74             "",
75             "@Module",
76             "interface TestModule {",
77             "  @Provides",
78             "  static String provideString() { return \"test\"; }",
79             "}");
80 
81     CompilerTests.daggerCompiler(componentFile, moduleFile)
82         .compile(
83             subject -> {
84               subject.hasErrorCount(1);
85               subject.hasErrorContaining(
86                   "The method test.TestComponent.builder() conflicts with a method");
87             });
88   }
89 
90   @Test
componentOnOverridingCreate_failsWhenGeneratedCreateMethod()91   public void componentOnOverridingCreate_failsWhenGeneratedCreateMethod() {
92     Source componentFile =
93         CompilerTests.javaSource(
94             "test.TestComponent",
95             "package test;",
96             "",
97             "import dagger.Component;",
98             "",
99             "@Component(modules=TestModule.class)",
100             "interface TestComponent {",
101             "  String create();",
102             "}");
103     Source moduleFile =
104         CompilerTests.javaSource(
105             "test.TestModule",
106             "package test;",
107             "",
108             "import dagger.Module;",
109             "import dagger.Provides;",
110             "",
111             "@Module",
112             "interface TestModule {",
113             "  @Provides",
114             "  static String provideString() { return \"test\"; }",
115             "}");
116 
117     CompilerTests.daggerCompiler(componentFile, moduleFile)
118         .compile(
119             subject -> {
120               subject.hasErrorCount(1);
121               subject.hasErrorContaining(
122                   "The method test.TestComponent.create() conflicts with a method");
123             });
124   }
125 
126   @Test
subcomponentMethodNameBuilder_succeeds()127   public void subcomponentMethodNameBuilder_succeeds() {
128     Source componentFile =
129         CompilerTests.javaSource(
130             "test.TestComponent",
131             "package test;",
132             "",
133             "import dagger.Component;",
134             "",
135             "@Component",
136             "interface TestComponent {",
137             "  TestSubcomponent.Builder subcomponent();",
138             "}");
139     Source subcomponentFile =
140         CompilerTests.javaSource(
141             "test.TestSubcomponent",
142             "package test;",
143             "",
144             "import dagger.Subcomponent;",
145             "",
146             "@Subcomponent(modules=TestModule.class)",
147             "interface TestSubcomponent {",
148             "  String builder();",
149             "  @Subcomponent.Builder",
150             "  interface Builder {",
151             "    TestSubcomponent build();",
152             "  }",
153             "}");
154     Source moduleFile =
155         CompilerTests.javaSource(
156             "test.TestModule",
157             "package test;",
158             "",
159             "import dagger.Module;",
160             "import dagger.Provides;",
161             "",
162             "@Module",
163             "interface TestModule {",
164             "  @Provides",
165             "  static String provideString() { return \"test\"; }",
166             "}");
167 
168     CompilerTests.daggerCompiler(componentFile, subcomponentFile, moduleFile)
169         .compile(subject -> subject.hasErrorCount(0));
170   }
171 
componentOnEnum()172   @Test public void componentOnEnum() {
173     Source componentFile =
174         CompilerTests.javaSource(
175             "test.NotAComponent",
176             "package test;",
177             "",
178             "import dagger.Component;",
179             "",
180             "@Component",
181             "enum NotAComponent {",
182             "  INSTANCE",
183             "}");
184     CompilerTests.daggerCompiler(componentFile)
185         .compile(
186             subject -> {
187               subject.hasErrorCount(1);
188               subject.hasErrorContaining("interface");
189             });
190   }
191 
componentOnAnnotation()192   @Test public void componentOnAnnotation() {
193     Source componentFile =
194         CompilerTests.javaSource(
195             "test.NotAComponent",
196             "package test;",
197             "",
198             "import dagger.Component;",
199             "",
200             "@Component",
201             "@interface NotAComponent {}");
202     CompilerTests.daggerCompiler(componentFile)
203         .compile(
204             subject -> {
205               subject.hasErrorCount(1);
206               subject.hasErrorContaining("interface");
207             });
208   }
209 
nonModuleModule()210   @Test public void nonModuleModule() {
211     Source componentFile =
212         CompilerTests.javaSource(
213             "test.NotAComponent",
214             "package test;",
215             "",
216             "import dagger.Component;",
217             "",
218             "@Component(modules = Object.class)",
219             "interface NotAComponent {}");
220     CompilerTests.daggerCompiler(componentFile)
221         .compile(
222             subject -> {
223               subject.hasErrorCount(1);
224               subject.hasErrorContaining("is not annotated with @Module");
225             });
226   }
227 
228   @Test
componentWithInvalidModule()229   public void componentWithInvalidModule() {
230     Source module =
231         CompilerTests.javaSource(
232             "test.BadModule",
233             "package test;",
234             "",
235             "import dagger.Binds;",
236             "import dagger.Module;",
237             "",
238             "@Module",
239             "abstract class BadModule {",
240             "  @Binds abstract Object noParameters();",
241             "}");
242     Source component =
243         CompilerTests.javaSource(
244             "test.BadComponent",
245             "package test;",
246             "",
247             "import dagger.Component;",
248             "",
249             "@Component(modules = BadModule.class)",
250             "interface BadComponent {",
251             "  Object object();",
252             "}");
253     CompilerTests.daggerCompiler(module, component)
254         .compile(
255             subject -> {
256               subject.hasErrorCount(2);
257               subject.hasErrorContaining("test.BadModule has errors")
258                   .onSource(component)
259                   .onLine(5);
260               subject.hasErrorContaining(
261                       "@Binds methods must have exactly one parameter, whose type is assignable to "
262                           + "the return type")
263                   .onSource(module)
264                   .onLine(8);
265             });
266   }
267 
268   @Test
attemptToInjectWildcardGenerics()269   public void attemptToInjectWildcardGenerics() {
270     Source testComponent =
271         CompilerTests.javaSource(
272             "test.TestComponent",
273             "package test;",
274             "",
275             "import dagger.Component;",
276             "import dagger.Lazy;",
277             "import javax.inject.Provider;",
278             "",
279             "@Component",
280             "interface TestComponent {",
281             "  Lazy<? extends Number> wildcardNumberLazy();",
282             "  Provider<? super Number> wildcardNumberProvider();",
283             "}");
284     CompilerTests.daggerCompiler(testComponent)
285         .compile(
286             subject -> {
287               subject.hasErrorCount(2);
288               subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(9);
289               subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(10);
290             });
291   }
292 
293   // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed.
294   @Test
invalidComponentDependencies()295   public void invalidComponentDependencies() {
296     JavaFileObject testComponent =
297         JavaFileObjects.forSourceLines(
298             "test.TestComponent",
299             "package test;",
300             "",
301             "import dagger.Component;",
302             "",
303             "@Component(dependencies = int.class)",
304             "interface TestComponent {}");
305     Compilation compilation = daggerCompiler().compile(testComponent);
306     assertThat(compilation).failed();
307     assertThat(compilation).hadErrorContaining("int is not a valid component dependency type");
308   }
309 
310   // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed.
311   @Test
invalidComponentModules()312   public void invalidComponentModules() {
313     JavaFileObject testComponent =
314         JavaFileObjects.forSourceLines(
315             "test.TestComponent",
316             "package test;",
317             "",
318             "import dagger.Component;",
319             "",
320             "@Component(modules = int.class)",
321             "interface TestComponent {}");
322     Compilation compilation = daggerCompiler().compile(testComponent);
323     assertThat(compilation).failed();
324     assertThat(compilation).hadErrorContaining("int is not a valid module type");
325   }
326 
327   @Test
moduleInDependencies()328   public void moduleInDependencies() {
329     Source testModule =
330         CompilerTests.javaSource(
331             "test.TestModule",
332             "package test;",
333             "",
334             "import dagger.Module;",
335             "import dagger.Provides;",
336             "",
337             "@Module",
338             "final class TestModule {",
339             "  @Provides String s() { return null; }",
340             "}");
341     Source testComponent =
342         CompilerTests.javaSource(
343             "test.TestComponent",
344             "package test;",
345             "",
346             "import dagger.Component;",
347             "",
348             "@Component(dependencies = TestModule.class)",
349             "interface TestComponent {}");
350     CompilerTests.daggerCompiler(testModule, testComponent)
351         .compile(
352             subject -> {
353               subject.hasErrorCount(1);
354               subject.hasErrorContaining(
355                   "test.TestModule is a module, which cannot be a component dependency");
356             });
357   }
358 
359   @Test
componentDependencyMustNotCycle_Direct()360   public void componentDependencyMustNotCycle_Direct() {
361     Source shortLifetime =
362         CompilerTests.javaSource(
363             "test.ComponentShort",
364             "package test;",
365             "",
366             "import dagger.Component;",
367             "",
368             "@Component(dependencies = ComponentShort.class)",
369             "interface ComponentShort {",
370             "}");
371 
372     String errorMessage =
373         message(
374             "test.ComponentShort contains a cycle in its component dependencies:",
375             "    test.ComponentShort");
376     CompilerTests.daggerCompiler(shortLifetime)
377         .compile(
378             subject -> {
379               subject.hasErrorCount(1);
380               subject.hasErrorContaining(errorMessage);
381             });
382 
383     // Test that this also fails when transitive validation is disabled.
384     CompilerTests.daggerCompiler(shortLifetime)
385         .withProcessingOptions(
386             ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED"))
387         .compile(
388             subject -> {
389               subject.hasErrorCount(1);
390               subject.hasErrorContaining(errorMessage);
391             });
392   }
393 
394   @Test
componentDependencyMustNotCycle_Indirect()395   public void componentDependencyMustNotCycle_Indirect() {
396     Source longLifetime =
397         CompilerTests.javaSource(
398             "test.ComponentLong",
399             "package test;",
400             "",
401             "import dagger.Component;",
402             "",
403             "@Component(dependencies = ComponentMedium.class)",
404             "interface ComponentLong {",
405             "}");
406     Source mediumLifetime =
407         CompilerTests.javaSource(
408             "test.ComponentMedium",
409             "package test;",
410             "",
411             "import dagger.Component;",
412             "",
413             "@Component(dependencies = ComponentLong.class)",
414             "interface ComponentMedium {",
415             "}");
416     Source shortLifetime =
417         CompilerTests.javaSource(
418             "test.ComponentShort",
419             "package test;",
420             "",
421             "import dagger.Component;",
422             "",
423             "@Component(dependencies = ComponentMedium.class)",
424             "interface ComponentShort {",
425             "}");
426 
427     CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime)
428         .compile(
429             subject -> {
430               subject.hasErrorCount(3);
431               subject.hasErrorContaining(
432                       message(
433                           "test.ComponentLong contains a cycle in its component dependencies:",
434                           "    test.ComponentLong",
435                           "    test.ComponentMedium",
436                           "    test.ComponentLong"))
437                   .onSource(longLifetime);
438               subject.hasErrorContaining(
439                       message(
440                           "test.ComponentMedium contains a cycle in its component dependencies:",
441                           "    test.ComponentMedium",
442                           "    test.ComponentLong",
443                           "    test.ComponentMedium"))
444                   .onSource(mediumLifetime);
445               subject.hasErrorContaining(
446                       message(
447                           "test.ComponentShort contains a cycle in its component dependencies:",
448                           "    test.ComponentMedium",
449                           "    test.ComponentLong",
450                           "    test.ComponentMedium",
451                           "    test.ComponentShort"))
452                   .onSource(shortLifetime);
453             });
454 
455     // Test that compilation succeeds when transitive validation is disabled because the cycle
456     // cannot be detected.
457     CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime)
458         .withProcessingOptions(
459             ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED"))
460         .compile(subject -> subject.hasErrorCount(0));
461   }
462 
463   @Test
abstractModuleWithInstanceMethod()464   public void abstractModuleWithInstanceMethod() {
465     Source module =
466         CompilerTests.javaSource(
467             "test.TestModule",
468             "package test;",
469             "",
470             "import dagger.Module;",
471             "import dagger.Provides;",
472             "",
473             "@Module",
474             "abstract class TestModule {",
475             "  @Provides int i() { return 1; }",
476             "}");
477     Source component =
478         CompilerTests.javaSource(
479             "test.TestComponent",
480             "package test;",
481             "",
482             "import dagger.Component;",
483             "",
484             "@Component(modules = TestModule.class)",
485             "interface TestComponent {",
486             "  int i();",
487             "}");
488     CompilerTests.daggerCompiler(module, component)
489         .compile(
490             subject -> {
491               subject.hasErrorCount(1);
492               subject.hasErrorContaining(
493                       "TestModule is abstract and has instance @Provides methods")
494                   .onSource(component)
495                   .onLineContaining("interface TestComponent");
496             });
497   }
498 
499   @Test
abstractModuleWithInstanceMethod_subclassedIsAllowed()500   public void abstractModuleWithInstanceMethod_subclassedIsAllowed() {
501     Source abstractModule =
502         CompilerTests.javaSource(
503             "test.AbstractModule",
504             "package test;",
505             "",
506             "import dagger.Module;",
507             "import dagger.Provides;",
508             "",
509             "@Module",
510             "abstract class AbstractModule {",
511             "  @Provides int i() { return 1; }",
512             "}");
513     Source subclassedModule =
514         CompilerTests.javaSource(
515             "test.SubclassedModule",
516             "package test;",
517             "",
518             "import dagger.Module;",
519             "",
520             "@Module",
521             "class SubclassedModule extends AbstractModule {}");
522     Source component =
523         CompilerTests.javaSource(
524             "test.TestComponent",
525             "package test;",
526             "",
527             "import dagger.Component;",
528             "",
529             "@Component(modules = SubclassedModule.class)",
530             "interface TestComponent {",
531             "  int i();",
532             "}");
533     CompilerTests.daggerCompiler(abstractModule, subclassedModule, component)
534         .compile(subject -> subject.hasErrorCount(0));
535   }
536 }
537