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.compilerWithOptions;
21 import static dagger.internal.codegen.Compilers.daggerCompiler;
22 import static dagger.internal.codegen.TestUtils.message;
23 
24 import com.google.testing.compile.Compilation;
25 import com.google.testing.compile.JavaFileObjects;
26 import javax.tools.JavaFileObject;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.junit.runners.JUnit4;
30 
31 @RunWith(JUnit4.class)
32 public final class ComponentValidationTest {
33   @Test
componentOnConcreteClass()34   public void componentOnConcreteClass() {
35     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.NotAComponent",
36         "package test;",
37         "",
38         "import dagger.Component;",
39         "",
40         "@Component",
41         "final class NotAComponent {}");
42     Compilation compilation = daggerCompiler().compile(componentFile);
43     assertThat(compilation).failed();
44     assertThat(compilation).hadErrorContaining("interface");
45   }
46 
componentOnEnum()47   @Test public void componentOnEnum() {
48     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.NotAComponent",
49         "package test;",
50         "",
51         "import dagger.Component;",
52         "",
53         "@Component",
54         "enum NotAComponent {",
55         "  INSTANCE",
56         "}");
57     Compilation compilation = daggerCompiler().compile(componentFile);
58     assertThat(compilation).failed();
59     assertThat(compilation).hadErrorContaining("interface");
60   }
61 
componentOnAnnotation()62   @Test public void componentOnAnnotation() {
63     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.NotAComponent",
64         "package test;",
65         "",
66         "import dagger.Component;",
67         "",
68         "@Component",
69         "@interface NotAComponent {}");
70     Compilation compilation = daggerCompiler().compile(componentFile);
71     assertThat(compilation).failed();
72     assertThat(compilation).hadErrorContaining("interface");
73   }
74 
nonModuleModule()75   @Test public void nonModuleModule() {
76     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.NotAComponent",
77         "package test;",
78         "",
79         "import dagger.Component;",
80         "",
81         "@Component(modules = Object.class)",
82         "interface NotAComponent {}");
83     Compilation compilation = daggerCompiler().compile(componentFile);
84     assertThat(compilation).failed();
85     assertThat(compilation).hadErrorContaining("is not annotated with @Module");
86   }
87 
88   @Test
componentWithInvalidModule()89   public void componentWithInvalidModule() {
90     JavaFileObject module =
91         JavaFileObjects.forSourceLines(
92             "test.BadModule",
93             "package test;",
94             "",
95             "import dagger.Binds;",
96             "import dagger.Module;",
97             "",
98             "@Module",
99             "abstract class BadModule {",
100             "  @Binds abstract Object noParameters();",
101             "}");
102     JavaFileObject component =
103         JavaFileObjects.forSourceLines(
104             "test.BadComponent",
105             "package test;",
106             "",
107             "import dagger.Component;",
108             "",
109             "@Component(modules = BadModule.class)",
110             "interface BadComponent {",
111             "  Object object();",
112             "}");
113     Compilation compilation = daggerCompiler().compile(module, component);
114     assertThat(compilation)
115         .hadErrorContaining("test.BadModule has errors")
116         .inFile(component)
117         .onLine(5);
118   }
119 
120   @Test
attemptToInjectWildcardGenerics()121   public void attemptToInjectWildcardGenerics() {
122     JavaFileObject testComponent =
123         JavaFileObjects.forSourceLines(
124             "test.TestComponent",
125             "package test;",
126             "",
127             "import dagger.Component;",
128             "import dagger.Lazy;",
129             "import javax.inject.Provider;",
130             "",
131             "@Component",
132             "interface TestComponent {",
133             "  Lazy<? extends Number> wildcardNumberLazy();",
134             "  Provider<? super Number> wildcardNumberProvider();",
135             "}");
136     Compilation compilation = daggerCompiler().compile(testComponent);
137     assertThat(compilation).failed();
138     assertThat(compilation).hadErrorContaining("wildcard type").inFile(testComponent).onLine(9);
139     assertThat(compilation).hadErrorContaining("wildcard type").inFile(testComponent).onLine(10);
140   }
141 
142   @Test
invalidComponentDependencies()143   public void invalidComponentDependencies() {
144     JavaFileObject testComponent =
145         JavaFileObjects.forSourceLines(
146             "test.TestComponent",
147             "package test;",
148             "",
149             "import dagger.Component;",
150             "",
151             "@Component(dependencies = int.class)",
152             "interface TestComponent {}");
153     Compilation compilation = daggerCompiler().compile(testComponent);
154     assertThat(compilation).failed();
155     assertThat(compilation).hadErrorContaining("int is not a valid component dependency type");
156   }
157 
158   @Test
invalidComponentModules()159   public void invalidComponentModules() {
160     JavaFileObject testComponent =
161         JavaFileObjects.forSourceLines(
162             "test.TestComponent",
163             "package test;",
164             "",
165             "import dagger.Component;",
166             "",
167             "@Component(modules = int.class)",
168             "interface TestComponent {}");
169     Compilation compilation = daggerCompiler().compile(testComponent);
170     assertThat(compilation).failed();
171     assertThat(compilation).hadErrorContaining("int is not a valid module type");
172   }
173 
174   @Test
moduleInDependencies()175   public void moduleInDependencies() {
176     JavaFileObject testModule =
177         JavaFileObjects.forSourceLines(
178             "test.TestModule",
179             "package test;",
180             "",
181             "import dagger.Module;",
182             "import dagger.Provides;",
183             "",
184             "@Module",
185             "final class TestModule {",
186             "  @Provides String s() { return null; }",
187             "}");
188     JavaFileObject testComponent =
189         JavaFileObjects.forSourceLines(
190             "test.TestComponent",
191             "package test;",
192             "",
193             "import dagger.Component;",
194             "",
195             "@Component(dependencies = TestModule.class)",
196             "interface TestComponent {}");
197     Compilation compilation = daggerCompiler().compile(testModule, testComponent);
198     assertThat(compilation).failed();
199     assertThat(compilation)
200         .hadErrorContaining("test.TestModule is a module, which cannot be a component dependency");
201   }
202 
203   @Test
componentDependencyMustNotCycle_Direct()204   public void componentDependencyMustNotCycle_Direct() {
205     JavaFileObject shortLifetime =
206         JavaFileObjects.forSourceLines(
207             "test.ComponentShort",
208             "package test;",
209             "",
210             "import dagger.Component;",
211             "",
212             "@Component(dependencies = ComponentShort.class)",
213             "interface ComponentShort {",
214             "}");
215 
216     Compilation compilation = daggerCompiler().compile(shortLifetime);
217     assertThat(compilation).failed();
218     assertThat(compilation)
219         .hadErrorContaining(
220             message(
221                 "test.ComponentShort contains a cycle in its component dependencies:",
222                 "    test.ComponentShort"));
223 
224     // Test that this also fails when transitive validation is disabled.
225     compilation =
226         compilerWithOptions("-Adagger.validateTransitiveComponentDependencies=DISABLED")
227             .compile(shortLifetime);
228     assertThat(compilation).failed();
229     assertThat(compilation)
230         .hadErrorContaining(
231             message(
232                 "test.ComponentShort contains a cycle in its component dependencies:",
233                 "    test.ComponentShort"));
234   }
235 
236   @Test
componentDependencyMustNotCycle_Indirect()237   public void componentDependencyMustNotCycle_Indirect() {
238     JavaFileObject longLifetime =
239         JavaFileObjects.forSourceLines(
240             "test.ComponentLong",
241             "package test;",
242             "",
243             "import dagger.Component;",
244             "",
245             "@Component(dependencies = ComponentMedium.class)",
246             "interface ComponentLong {",
247             "}");
248     JavaFileObject mediumLifetime =
249         JavaFileObjects.forSourceLines(
250             "test.ComponentMedium",
251             "package test;",
252             "",
253             "import dagger.Component;",
254             "",
255             "@Component(dependencies = ComponentLong.class)",
256             "interface ComponentMedium {",
257             "}");
258     JavaFileObject shortLifetime =
259         JavaFileObjects.forSourceLines(
260             "test.ComponentShort",
261             "package test;",
262             "",
263             "import dagger.Component;",
264             "",
265             "@Component(dependencies = ComponentMedium.class)",
266             "interface ComponentShort {",
267             "}");
268 
269     Compilation compilation = daggerCompiler().compile(longLifetime, mediumLifetime, shortLifetime);
270     assertThat(compilation).failed();
271     assertThat(compilation)
272         .hadErrorContaining(
273             message(
274                 "test.ComponentLong contains a cycle in its component dependencies:",
275                 "    test.ComponentLong",
276                 "    test.ComponentMedium",
277                 "    test.ComponentLong"))
278         .inFile(longLifetime);
279     assertThat(compilation)
280         .hadErrorContaining(
281             message(
282                 "test.ComponentMedium contains a cycle in its component dependencies:",
283                 "    test.ComponentMedium",
284                 "    test.ComponentLong",
285                 "    test.ComponentMedium"))
286         .inFile(mediumLifetime);
287     assertThat(compilation)
288         .hadErrorContaining(
289             message(
290                 "test.ComponentShort contains a cycle in its component dependencies:",
291                 "    test.ComponentMedium",
292                 "    test.ComponentLong",
293                 "    test.ComponentMedium",
294                 "    test.ComponentShort"))
295         .inFile(shortLifetime);
296 
297     // Test that compilation succeeds when transitive validation is disabled because the cycle
298     // cannot be detected.
299     compilation =
300         compilerWithOptions("-Adagger.validateTransitiveComponentDependencies=DISABLED")
301             .compile(longLifetime, mediumLifetime, shortLifetime);
302     assertThat(compilation).succeeded();
303   }
304 
305   @Test
abstractModuleWithInstanceMethod()306   public void abstractModuleWithInstanceMethod() {
307     JavaFileObject module =
308         JavaFileObjects.forSourceLines(
309             "test.TestModule",
310             "package test;",
311             "",
312             "import dagger.Module;",
313             "import dagger.Provides;",
314             "",
315             "@Module",
316             "abstract class TestModule {",
317             "  @Provides int i() { return 1; }",
318             "}");
319     JavaFileObject component =
320         JavaFileObjects.forSourceLines(
321             "test.TestComponent",
322             "package test;",
323             "",
324             "import dagger.Component;",
325             "",
326             "@Component(modules = TestModule.class)",
327             "interface TestComponent {",
328             "  int i();",
329             "}");
330     Compilation compilation = daggerCompiler().compile(module, component);
331     assertThat(compilation).failed();
332     assertThat(compilation)
333         .hadErrorContaining("TestModule is abstract and has instance @Provides methods")
334         .inFile(component)
335         .onLineContaining("interface TestComponent");
336   }
337 
338   @Test
abstractModuleWithInstanceMethod_subclassedIsAllowed()339   public void abstractModuleWithInstanceMethod_subclassedIsAllowed() {
340     JavaFileObject abstractModule =
341         JavaFileObjects.forSourceLines(
342             "test.AbstractModule",
343             "package test;",
344             "",
345             "import dagger.Module;",
346             "import dagger.Provides;",
347             "",
348             "@Module",
349             "abstract class AbstractModule {",
350             "  @Provides int i() { return 1; }",
351             "}");
352     JavaFileObject subclassedModule =
353         JavaFileObjects.forSourceLines(
354             "test.SubclassedModule",
355             "package test;",
356             "",
357             "import dagger.Module;",
358             "",
359             "@Module",
360             "class SubclassedModule extends AbstractModule {}");
361     JavaFileObject component =
362         JavaFileObjects.forSourceLines(
363             "test.TestComponent",
364             "package test;",
365             "",
366             "import dagger.Component;",
367             "",
368             "@Component(modules = SubclassedModule.class)",
369             "interface TestComponent {",
370             "  int i();",
371             "}");
372     Compilation compilation = daggerCompiler().compile(abstractModule, subclassedModule, component);
373     assertThat(compilation).succeeded();
374   }
375 }
376