1 /*
2  * Copyright (C) 2020 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.hilt.processor.internal.aggregateddeps;
18 
19 import static com.google.testing.compile.CompilationSubject.assertThat;
20 
21 import com.google.common.base.Joiner;
22 import com.google.testing.compile.Compilation;
23 import com.google.testing.compile.JavaFileObjects;
24 import dagger.hilt.processor.internal.GeneratedImport;
25 import dagger.testing.compile.CompilerTests;
26 import javax.tools.JavaFileObject;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.junit.runners.JUnit4;
30 
31 /** Tests for errors generated by {@link AggregatedDepsProcessor} */
32 @RunWith(JUnit4.class)
33 public class AggregatedDepsProcessorErrorsTest {
34   private static final Joiner LINES = Joiner.on("\n");
35 
36   @Test
reportMultipleAnnotationTypeKindErrors()37   public void reportMultipleAnnotationTypeKindErrors() {
38     JavaFileObject source =
39         JavaFileObjects.forSourceString(
40             "foo.bar.AnnotationsOnWrongTypeKind",
41             LINES.join(
42                 "package foo.bar;",
43                 "",
44                 "import dagger.hilt.EntryPoint;",
45                 "import dagger.hilt.InstallIn;",
46                 "import dagger.Module;",
47                 "import dagger.hilt.components.SingletonComponent;",
48                 "import dagger.hilt.internal.ComponentEntryPoint;",
49                 "import dagger.hilt.internal.GeneratedEntryPoint;",
50                 "",
51                 "@InstallIn(SingletonComponent.class)",
52                 "@Module",
53                 "enum FooModule { VALUE }",
54                 "",
55                 "@InstallIn(SingletonComponent.class)",
56                 "@EntryPoint",
57                 "final class BarEntryPoint {}",
58                 "",
59                 "@InstallIn(SingletonComponent.class)",
60                 "@ComponentEntryPoint",
61                 "final class BazComponentEntryPoint {}",
62                 "",
63                 "@EntryPoint",
64                 "interface QuxEntryPoint {}",
65                 "",
66                 "@EntryPoint",
67                 "@Module",
68                 "interface DontMix{}",
69                 ""));
70 
71     Compilation compilation =
72         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
73 
74     assertThat(compilation).failed();
75     assertThat(compilation)
76         .hadErrorContaining("Only classes and interfaces can be annotated with @Module")
77         .inFile(source)
78         .onLine(12);
79     assertThat(compilation)
80         .hadErrorContaining("Only interfaces can be annotated with @EntryPoint")
81         .inFile(source)
82         .onLine(16);
83     assertThat(compilation)
84         .hadErrorContaining("Only interfaces can be annotated with @ComponentEntryPoint")
85         .inFile(source)
86         .onLine(20);
87     assertThat(compilation)
88         .hadErrorContaining(
89             "@EntryPoint foo.bar.QuxEntryPoint must also be annotated with @InstallIn")
90         .inFile(source)
91         .onLine(23);
92     assertThat(compilation)
93         .hadErrorContaining("@Module and @EntryPoint cannot be used on the same interface")
94         .inFile(source)
95         .onLine(27);
96   }
97 
98   @Test
testInvalidComponentInInstallInAnnotation()99   public void testInvalidComponentInInstallInAnnotation() {
100     JavaFileObject module = JavaFileObjects.forSourceLines(
101         "test.FooModule",
102         "package test;",
103         "",
104         "import dagger.Module;",
105         "import dagger.hilt.InstallIn;",
106         "import dagger.hilt.android.qualifiers.ApplicationContext;",
107         "",
108         "@InstallIn(ApplicationContext.class)", // Error: Not a Hilt component
109         "@Module",
110         "final class FooModule {}");
111 
112     Compilation compilation =
113         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(module);
114 
115     assertThat(compilation).failed();
116     assertThat(compilation)
117         .hadErrorContaining(
118             "@InstallIn, can only be used with @DefineComponent-annotated classes, but found: "
119                 + "[dagger.hilt.android.qualifiers.ApplicationContext]")
120         .inFile(module)
121         .onLine(9);
122   }
123 
124   @Test
testMissingInstallInAnnotation()125   public void testMissingInstallInAnnotation() {
126     JavaFileObject source = JavaFileObjects.forSourceString(
127         "foo.bar.AnnotationsOnWrongTypeKind",
128         LINES.join(
129             "package foo.bar;",
130             "",
131             "import dagger.Module;",
132             "",
133             "@Module",     // Error: Doesn't have InstallIn annotation
134             "final class FooModule {}"));
135 
136     Compilation compilation =
137         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
138 
139     assertThat(compilation).failed();
140     assertThat(compilation)
141         .hadErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
142         .inFile(source)
143         .onLine(6);
144   }
145 
146   @Test
testNoErrorOnDaggerGeneratedModules()147   public void testNoErrorOnDaggerGeneratedModules() {
148     JavaFileObject source =
149         JavaFileObjects.forSourceString(
150             "foo.bar",
151             LINES.join(
152                 "package foo.bar;",
153                 "",
154                 GeneratedImport.IMPORT_GENERATED_ANNOTATION,
155                 "import dagger.Module;",
156                 "",
157                 "@Module",
158                 "@Generated(value = \"something\")", // Error: Isn't Dagger-generated but missing
159                                                      // InstallIn
160                 "final class FooModule {}",
161                 "",
162                 "@Module",
163                 "@Generated(value = \"dagger\")", // No error because the module is dagger generated
164                 "final class BarModule {}"));
165 
166     Compilation compilation =
167         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
168 
169     assertThat(compilation).failed();
170     assertThat(compilation).hadErrorCount(1);
171     assertThat(compilation)
172         .hadErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
173         .inFile(source)
174         .onLine(8);
175   }
176 
177   @Test
testModuleWithOnlyParamConstructor_fails()178   public void testModuleWithOnlyParamConstructor_fails() {
179     JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
180         "package foo.bar;",
181         "",
182         "import dagger.Module;",
183         "import dagger.Provides;",
184         "import dagger.hilt.InstallIn;",
185         "import dagger.hilt.components.SingletonComponent;",
186         "",
187         "@Module",
188         "@InstallIn(SingletonComponent.class)",
189         "final class FooModule {",
190         "  FooModule(String arg) {}",
191         "",
192         "  @Provides",
193         "  String provideString() {",
194         "    return \"\";",
195         "  }",
196         "}"));
197 
198     Compilation compilation =
199         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
200 
201     assertThat(compilation).failed();
202     assertThat(compilation).hadErrorCount(1);
203     assertThat(compilation)
204         .hadErrorContaining(
205             "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
206   }
207 
208   @Test
testInnerModule()209   public void testInnerModule() {
210     JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
211         "package foo.bar;",
212         "",
213         "import dagger.Module;",
214         "import dagger.hilt.InstallIn;",
215         "import dagger.hilt.components.SingletonComponent;",
216         "",
217         "final class Outer {",
218         "  @Module",
219         "  @InstallIn(SingletonComponent.class)",
220         "  final class InnerModule {}",
221         "}"));
222 
223     Compilation compilation =
224         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
225 
226     assertThat(compilation).failed();
227     assertThat(compilation).hadErrorCount(1);
228     assertThat(compilation)
229         .hadErrorContaining(
230             "Nested @InstallIn modules must be static unless they are directly nested within a "
231                 + "test. Found: foo.bar.Outer.InnerModule");
232   }
233 
234   @Test
testInnerModuleInTest()235   public void testInnerModuleInTest() {
236     JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
237         "package foo.bar;",
238         "",
239         "import dagger.Module;",
240         "import dagger.hilt.InstallIn;",
241         "import dagger.hilt.components.SingletonComponent;",
242         "import dagger.hilt.android.testing.HiltAndroidTest;",
243         "",
244         "@HiltAndroidTest",
245         "final class Outer {",
246         "  static class Nested {",
247         "    @Module",
248         "    @InstallIn(SingletonComponent.class)",
249         "    final class InnerModule {}",
250         "  }",
251         "}"));
252 
253     Compilation compilation =
254         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
255 
256     assertThat(compilation).failed();
257     assertThat(compilation).hadErrorCount(1);
258     assertThat(compilation)
259         .hadErrorContaining(
260             "Nested @InstallIn modules must be static unless they are directly nested within a "
261                 + "test. Found: foo.bar.Outer.Nested.InnerModule");
262   }
263 
264   @Test
testInnerModuleInTest_succeeds()265   public void testInnerModuleInTest_succeeds() {
266     JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
267         "package foo.bar;",
268         "",
269         "import dagger.Module;",
270         "import dagger.hilt.InstallIn;",
271         "import dagger.hilt.components.SingletonComponent;",
272         "import dagger.hilt.android.testing.HiltAndroidTest;",
273         "",
274         "@HiltAndroidTest",
275         "final class Outer {",
276         "  @Module",
277         "  @InstallIn(SingletonComponent.class)",
278         "  final class InnerModule {}",
279         "}"));
280 
281     Compilation compilation =
282         CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
283 
284     assertThat(compilation).succeeded();
285   }
286 }
287