xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/ModuleValidationTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1 /*
2  * Copyright (C) 2016 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 dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
20 
21 import androidx.room.compiler.processing.XProcessingEnv;
22 import androidx.room.compiler.processing.util.Source;
23 import dagger.Module;
24 import dagger.producers.ProducerModule;
25 import dagger.testing.compile.CompilerTests;
26 import java.lang.annotation.Annotation;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.junit.runners.Parameterized;
32 
33 @RunWith(Parameterized.class)
34 public final class ModuleValidationTest {
35 
36   @Parameterized.Parameters(name = "{0}")
parameters()37   public static Collection<Object[]> parameters() {
38     return Arrays.asList(new Object[][] {{ModuleType.MODULE}, {ModuleType.PRODUCER_MODULE}});
39   }
40 
41   private enum ModuleType {
42     MODULE(Module.class),
43     PRODUCER_MODULE(ProducerModule.class),
44     ;
45 
46     private final Class<? extends Annotation> annotation;
47 
ModuleType(Class<? extends Annotation> annotation)48     ModuleType(Class<? extends Annotation> annotation) {
49       this.annotation = annotation;
50     }
51 
annotationWithSubcomponent(String subcomponent)52     String annotationWithSubcomponent(String subcomponent) {
53       return String.format("@%s(subcomponents = %s)", annotation.getSimpleName(), subcomponent);
54     }
55 
importStatement()56     String importStatement() {
57       return String.format("import %s;", annotation.getName());
58     }
59 
simpleName()60     String simpleName() {
61       return annotation.getSimpleName();
62     }
63   }
64 
65   private final ModuleType moduleType;
66 
ModuleValidationTest(ModuleType moduleType)67   public ModuleValidationTest(ModuleType moduleType) {
68     this.moduleType = moduleType;
69   }
70 
71   @Test
moduleSubcomponents_notASubcomponent()72   public void moduleSubcomponents_notASubcomponent() {
73     Source module =
74         CompilerTests.javaSource(
75             "test.TestModule",
76             "package test;",
77             "",
78             moduleType.importStatement(),
79             "",
80             moduleType.annotationWithSubcomponent("NotASubcomponent.class"),
81             "class TestModule {}");
82     Source notASubcomponent =
83         CompilerTests.javaSource(
84             "test.NotASubcomponent",
85             "package test;",
86             "",
87             "class NotASubcomponent {}");
88     CompilerTests.daggerCompiler(module, notASubcomponent)
89         .compile(
90             subject -> {
91               subject.hasErrorCount(1);
92               subject.hasErrorContaining(
93                       "test.NotASubcomponent is not a @Subcomponent or @ProductionSubcomponent")
94                   .onSource(module)
95                   .onLine(5);
96             });
97   }
98 
99   @Test
moduleSubcomponents_listsSubcomponentBuilder()100   public void moduleSubcomponents_listsSubcomponentBuilder() {
101     Source module =
102         CompilerTests.javaSource(
103             "test.TestModule",
104             "package test;",
105             "",
106             moduleType.importStatement(),
107             "",
108             moduleType.annotationWithSubcomponent("Sub.Builder.class"),
109             "class TestModule {}");
110     Source subcomponent =
111         CompilerTests.javaSource(
112             "test.Sub",
113             "package test;",
114             "",
115             "import dagger.Subcomponent;",
116             "",
117             "@Subcomponent",
118             "interface Sub {",
119             "  @Subcomponent.Builder",
120             "  interface Builder {",
121             "    Sub build();",
122             "  }",
123             "}");
124     CompilerTests.daggerCompiler(module, subcomponent)
125         .compile(
126             subject -> {
127               subject.hasErrorCount(1);
128               subject.hasErrorContaining(
129                       "test.Sub.Builder is a @Subcomponent.Builder. Did you mean to use test.Sub?")
130                   .onSource(module)
131                   .onLine(5);
132             });
133   }
134 
135   @Test
moduleSubcomponents_listsSubcomponentFactory()136   public void moduleSubcomponents_listsSubcomponentFactory() {
137     Source module =
138         CompilerTests.javaSource(
139             "test.TestModule",
140             "package test;",
141             "",
142             moduleType.importStatement(),
143             "",
144             moduleType.annotationWithSubcomponent("Sub.Factory.class"),
145             "class TestModule {}");
146     Source subcomponent =
147         CompilerTests.javaSource(
148             "test.Sub",
149             "package test;",
150             "",
151             "import dagger.Subcomponent;",
152             "",
153             "@Subcomponent",
154             "interface Sub {",
155             "  @Subcomponent.Factory",
156             "  interface Factory {",
157             "    Sub creator();",
158             "  }",
159             "}");
160     CompilerTests.daggerCompiler(module, subcomponent)
161         .compile(
162             subject -> {
163               subject.hasErrorCount(1);
164               subject.hasErrorContaining(
165                       "test.Sub.Factory is a @Subcomponent.Factory. Did you mean to use test.Sub?")
166                   .onSource(module)
167                   .onLine(5);
168             });
169   }
170 
171   @Test
moduleSubcomponents_listsProductionSubcomponentBuilder()172   public void moduleSubcomponents_listsProductionSubcomponentBuilder() {
173     Source module =
174         CompilerTests.javaSource(
175             "test.TestModule",
176             "package test;",
177             "",
178             moduleType.importStatement(),
179             "",
180             moduleType.annotationWithSubcomponent("Sub.Builder.class"),
181             "class TestModule {}");
182     Source subcomponent =
183         CompilerTests.javaSource(
184             "test.Sub",
185             "package test;",
186             "",
187             "import dagger.producers.ProductionSubcomponent;",
188             "",
189             "@ProductionSubcomponent",
190             "interface Sub {",
191             "  @ProductionSubcomponent.Builder",
192             "  interface Builder {",
193             "    Sub build();",
194             "  }",
195             "}");
196     CompilerTests.daggerCompiler(module, subcomponent)
197         .compile(
198             subject -> {
199               subject.hasErrorCount(1);
200               subject.hasErrorContaining(
201                       "test.Sub.Builder is a @ProductionSubcomponent.Builder. "
202                           + "Did you mean to use test.Sub?")
203                   .onSource(module)
204                   .onLine(5);
205             });
206   }
207 
208   @Test
moduleSubcomponents_listsProductionSubcomponentFactory()209   public void moduleSubcomponents_listsProductionSubcomponentFactory() {
210     Source module =
211         CompilerTests.javaSource(
212             "test.TestModule",
213             "package test;",
214             "",
215             moduleType.importStatement(),
216             "",
217             moduleType.annotationWithSubcomponent("Sub.Factory.class"),
218             "class TestModule {}");
219     Source subcomponent =
220         CompilerTests.javaSource(
221             "test.Sub",
222             "package test;",
223             "",
224             "import dagger.producers.ProductionSubcomponent;",
225             "",
226             "@ProductionSubcomponent",
227             "interface Sub {",
228             "  @ProductionSubcomponent.Factory",
229             "  interface Factory {",
230             "    Sub create();",
231             "  }",
232             "}");
233     CompilerTests.daggerCompiler(module, subcomponent)
234         .compile(
235             subject -> {
236               subject.hasErrorCount(1);
237               subject.hasErrorContaining(
238                       "test.Sub.Factory is a @ProductionSubcomponent.Factory. "
239                           + "Did you mean to use test.Sub?")
240                   .onSource(module)
241                   .onLine(5);
242             });
243   }
244 
245   @Test
moduleSubcomponents_noSubcomponentCreator()246   public void moduleSubcomponents_noSubcomponentCreator() {
247     Source module =
248         CompilerTests.javaSource(
249             "test.TestModule",
250             "package test;",
251             "",
252             moduleType.importStatement(),
253             "",
254             moduleType.annotationWithSubcomponent("NoBuilder.class"),
255             "class TestModule {}");
256     Source subcomponent =
257         CompilerTests.javaSource(
258             "test.NoBuilder",
259             "package test;",
260             "",
261             "import dagger.Subcomponent;",
262             "",
263             "@Subcomponent",
264             "interface NoBuilder {}");
265     CompilerTests.daggerCompiler(module, subcomponent)
266         .compile(
267             subject -> {
268               subject.hasErrorCount(1);
269               subject.hasErrorContaining(
270                       "test.NoBuilder doesn't have a @Subcomponent.Builder or "
271                           + "@Subcomponent.Factory, which is required when used with @"
272                           + moduleType.simpleName()
273                           + ".subcomponents")
274                   .onSource(module)
275                   .onLine(5);
276             });
277   }
278 
279   @Test
moduleSubcomponents_noProductionSubcomponentCreator()280   public void moduleSubcomponents_noProductionSubcomponentCreator() {
281     Source module =
282         CompilerTests.javaSource(
283             "test.TestModule",
284             "package test;",
285             "",
286             moduleType.importStatement(),
287             "",
288             moduleType.annotationWithSubcomponent("NoBuilder.class"),
289             "class TestModule {}");
290     Source subcomponent =
291         CompilerTests.javaSource(
292             "test.NoBuilder",
293             "package test;",
294             "",
295             "import dagger.producers.ProductionSubcomponent;",
296             "",
297             "@ProductionSubcomponent",
298             "interface NoBuilder {}");
299     CompilerTests.daggerCompiler(module, subcomponent)
300         .compile(
301             subject -> {
302               subject.hasErrorCount(1);
303               subject.hasErrorContaining(
304                       "test.NoBuilder doesn't have a @ProductionSubcomponent.Builder or "
305                           + "@ProductionSubcomponent.Factory, which is required when used with @"
306                           + moduleType.simpleName()
307                           + ".subcomponents")
308                   .onSource(module)
309                   .onLine(5);
310             });
311   }
312 
313   @Test
moduleSubcomponentsAreTypes()314   public void moduleSubcomponentsAreTypes() {
315     Source module =
316         CompilerTests.javaSource(
317             "test.TestModule",
318             "package test;",
319             "",
320             "import dagger.Module;",
321             "",
322             "@Module(subcomponents = int.class)",
323             "class TestModule {}");
324     CompilerTests.daggerCompiler(module)
325         .compile(
326             subject -> {
327               subject.hasErrorCount(1);
328               switch (CompilerTests.backend(subject)) {
329                 case JAVAC:
330                   subject.hasErrorContaining("int is not a valid subcomponent type")
331                       .onSource(module)
332                       .onLine(5);
333                   break;
334                 case KSP:
335                   // TODO(b/245954367): Remove this pathway once this bug is fixed.
336                   // KSP interprets the int.class type as a boxed type so we get a slightly
337                   // different error message for now.
338                   subject.hasErrorContaining(
339                           "java.lang.Integer is not a @Subcomponent or @ProductionSubcomponent")
340                       .onSource(module)
341                       .onLine(5);
342                   break;
343               }
344             });
345   }
346 
347   @Test
tooManyAnnotations()348   public void tooManyAnnotations() {
349     assertThatModuleMethod(
350             "@BindsOptionalOf @Multibinds abstract Set<Object> tooManyAnnotations();")
351         .hasError("is annotated with more than one of");
352   }
353 
354   @Test
invalidIncludedModule()355   public void invalidIncludedModule() {
356     Source badModule =
357         CompilerTests.javaSource(
358             "test.BadModule",
359             "package test;",
360             "",
361             "import dagger.Binds;",
362             "import dagger.Module;",
363             "",
364             "@Module",
365             "abstract class BadModule {",
366             "  @Binds abstract Object noParameters();",
367             "}");
368     Source module =
369         CompilerTests.javaSource(
370             "test.IncludesBadModule",
371             "package test;",
372             "",
373             "import dagger.Module;",
374             "",
375             "@Module(includes = BadModule.class)",
376             "abstract class IncludesBadModule {}");
377     CompilerTests.daggerCompiler(badModule, module)
378         .compile(
379             subject -> {
380               subject.hasErrorCount(2);
381               subject.hasErrorContaining("test.BadModule has errors")
382                   .onSource(module)
383                   .onLine(5);
384               subject.hasErrorContaining(
385                       "@Binds methods must have exactly one parameter, whose type is "
386                           + "assignable to the return type")
387                   .onSource(badModule)
388                   .onLine(8);
389             });
390   }
391 
392   @Test
scopeOnModule()393   public void scopeOnModule() {
394     Source badModule =
395         CompilerTests.javaSource(
396             "test.BadModule",
397             "package test;",
398             "",
399             "import dagger.Module;",
400             "import javax.inject.Singleton;",
401             "",
402             "@Singleton",
403             "@Module",
404             "interface BadModule {}");
405     CompilerTests.daggerCompiler(badModule)
406         .compile(
407             subject -> {
408               subject.hasErrorCount(1);
409               subject.hasErrorContaining("@Modules cannot be scoped")
410                   .onSource(badModule)
411                   .onLineContaining("@Singleton");
412             });
413   }
414 
415   @Test
moduleIncludesSelfCycle()416   public void moduleIncludesSelfCycle() {
417     Source module =
418         CompilerTests.javaSource(
419             "test.TestModule",
420             "package test;",
421             "",
422             moduleType.importStatement(),
423             "import dagger.Provides;",
424             "",
425             String.format("@%s(", moduleType.simpleName()),
426             "  includes = {",
427             "      TestModule.class, // first",
428             "      OtherModule.class,",
429             "      TestModule.class, // second",
430             "  }",
431             ")",
432             "class TestModule {",
433             "  @Provides int i() { return 0; }",
434             "}");
435 
436     Source otherModule =
437         CompilerTests.javaSource(
438             "test.OtherModule",
439             "package test;",
440             "",
441             "import dagger.Module;",
442             "",
443             "@Module",
444             "class OtherModule {}");
445 
446     CompilerTests.daggerCompiler(module, otherModule)
447         .compile(
448             subject -> {
449               subject.hasErrorCount(2);
450               String error =
451                   String.format("@%s cannot include themselves", moduleType.simpleName());
452               switch (CompilerTests.backend(subject)) {
453                 case JAVAC:
454                   subject.hasErrorContaining(error).onSource(module).onLineContaining("// first");
455                   subject.hasErrorContaining(error).onSource(module).onLineContaining("// second");
456                   break;
457                 case KSP:
458                   // KSP doesn't support reporting errors on individual annotation values, so both
459                   // errors will be reported on the annotation itself.
460                   subject.hasErrorContaining(error)
461                       .onSource(module)
462                       .onLineContaining("@" + moduleType.simpleName());
463                   break;
464               }
465             });
466   }
467 
468   // Regression test for b/264618194.
469   @Test
objectModuleInheritsInstanceBindingFails()470   public void objectModuleInheritsInstanceBindingFails() {
471     Source objectModule =
472         CompilerTests.kotlinSource(
473             "test.ObjectModule.kt",
474             "package test",
475             "",
476             "import dagger.Module",
477             "import dagger.Provides",
478             "",
479             "@Module",
480             "object ObjectModule : ClassModule() {",
481             "  @Provides fun provideString(): String = \"\"",
482             "}");
483     Source classModule =
484         CompilerTests.kotlinSource(
485             "test.ClassModule.kt",
486             "package test",
487             "",
488             "import dagger.Module",
489             "import dagger.Provides",
490             "",
491             "@Module",
492             "abstract class ClassModule {",
493             "  @Provides fun provideInt(): Int = 1",
494             "}");
495     Source component =
496         CompilerTests.kotlinSource(
497             "test.TestComponent.kt",
498             "package test",
499             "",
500             "import dagger.Component",
501             "",
502             "@Component(modules = [ObjectModule::class])",
503             "interface TestComponent {",
504             "  fun getInt(): Int",
505             "  fun getString(): String",
506             "}");
507 
508     CompilerTests.daggerCompiler(component, objectModule, classModule)
509         .compile(
510             subject -> {
511               subject.hasErrorCount(2);
512               subject.hasErrorContaining("test.ObjectModule has errors")
513                   .onSource(component)
514                   .onLineContaining("ObjectModule::class");
515               subject.hasErrorContaining(
516                       "@Module-annotated Kotlin object cannot inherit instance "
517                           + "(i.e. non-abstract, non-JVM static) binding method: "
518                           + "@Provides int test.ClassModule.provideInt()")
519                   .onSource(objectModule)
520                   .onLineContaining(
521                       // TODO(b/267223703): KAPT incorrectly reports the error on the annotation.
522                       CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
523                           ? "@Module"
524                           : "object ObjectModule");
525             });
526   }
527 
528   // Regression test for b/264618194.
529   @Test
objectModuleInheritsNonInstanceBindingSucceeds()530   public void objectModuleInheritsNonInstanceBindingSucceeds() {
531     Source objectModule =
532         CompilerTests.kotlinSource(
533             "test.ObjectModule.kt",
534             "package test",
535             "",
536             "import dagger.Module",
537             "import dagger.Provides",
538             "",
539             "@Module",
540             "object ObjectModule : ClassModule() {",
541             "  @Provides fun provideString(): String = \"\"",
542             "}");
543     Source classModule =
544         CompilerTests.javaSource(
545             "test.ClassModule",
546             "package test;",
547             "",
548             "import dagger.Binds;",
549             "import dagger.Module;",
550             "import dagger.Provides;",
551             "",
552             "@Module",
553             "public abstract class ClassModule {",
554             "  // A non-binding instance method is okay.",
555             "  public int nonBindingMethod() {",
556             "    return 1;",
557             "  }",
558             "",
559             "  // A static binding method is also okay.",
560             "  @Provides",
561             "  public static int provideInt() {",
562             "    return 1;",
563             "  }",
564             "}");
565     Source component =
566         CompilerTests.kotlinSource(
567             "test.TestComponent.kt",
568             "package test",
569             "",
570             "import dagger.Component",
571             "",
572             "@Component(modules = [ObjectModule::class])",
573             "interface TestComponent {",
574             "  fun getInt(): Int",
575             "  fun getString(): String",
576             "}");
577     CompilerTests.daggerCompiler(component, objectModule, classModule)
578         .compile(subject -> subject.hasErrorCount(0));
579   }
580 }
581