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 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 /** Tests for {ComponentHierarchyValidator}. */
32 @RunWith(JUnit4.class)
33 public class ComponentHierarchyValidationTest {
34   @Test
singletonSubcomponent()35   public void singletonSubcomponent() {
36     JavaFileObject component =
37         JavaFileObjects.forSourceLines(
38             "test.Parent",
39             "package test;",
40             "",
41             "import dagger.Component;",
42             "import javax.inject.Singleton;",
43             "",
44             "@Singleton",
45             "@Component",
46             "interface Parent {",
47             "  Child child();",
48             "}");
49     JavaFileObject subcomponent =
50         JavaFileObjects.forSourceLines(
51             "test.Child",
52             "package test;",
53             "",
54             "import dagger.Subcomponent;",
55             "import javax.inject.Singleton;",
56             "",
57             "@Singleton",
58             "@Subcomponent",
59             "interface Child {}");
60 
61     Compilation compilation = daggerCompiler().compile(component, subcomponent);
62     assertThat(compilation).failed();
63     assertThat(compilation).hadErrorContaining("conflicting scopes");
64     assertThat(compilation).hadErrorContaining("test.Parent also has @Singleton");
65 
66     Compilation withoutScopeValidation =
67         compilerWithOptions("-Adagger.disableInterComponentScopeValidation=none")
68             .compile(component, subcomponent);
69     assertThat(withoutScopeValidation).succeeded();
70   }
71 
72   @Test
productionComponents_productionScopeImplicitOnBoth()73   public void productionComponents_productionScopeImplicitOnBoth() {
74     JavaFileObject component =
75         JavaFileObjects.forSourceLines(
76             "test.Parent",
77             "package test;",
78             "",
79             "import dagger.producers.ProductionComponent;",
80             "",
81             "@ProductionComponent(modules = ParentModule.class)",
82             "interface Parent {",
83             "  Child child();",
84             "  Object productionScopedObject();",
85             "}");
86     JavaFileObject parentModule =
87         JavaFileObjects.forSourceLines(
88             "test.ParentModule",
89             "package test;",
90             "",
91             "import dagger.Provides;",
92             "import dagger.producers.ProducerModule;",
93             "import dagger.producers.ProductionScope;",
94             "",
95             "@ProducerModule",
96             "class ParentModule {",
97             "  @Provides @ProductionScope Object parentScopedObject() { return new Object(); }",
98             "}");
99     JavaFileObject subcomponent =
100         JavaFileObjects.forSourceLines(
101             "test.Child",
102             "package test;",
103             "",
104             "import dagger.producers.ProductionSubcomponent;",
105             "",
106             "@ProductionSubcomponent(modules = ChildModule.class)",
107             "interface Child {",
108             "  String productionScopedString();",
109             "}");
110     JavaFileObject childModule =
111         JavaFileObjects.forSourceLines(
112             "test.ChildModule",
113             "package test;",
114             "",
115             "import dagger.Provides;",
116             "import dagger.producers.ProducerModule;",
117             "import dagger.producers.ProductionScope;",
118             "",
119             "@ProducerModule",
120             "class ChildModule {",
121             "  @Provides @ProductionScope String childScopedString() { return new String(); }",
122             "}");
123     Compilation compilation =
124         daggerCompiler().compile(component, subcomponent, parentModule, childModule);
125     assertThat(compilation).succeeded();
126   }
127 
128   @Test
producerModuleRepeated()129   public void producerModuleRepeated() {
130     JavaFileObject component =
131         JavaFileObjects.forSourceLines(
132             "test.Parent",
133             "package test;",
134             "",
135             "import dagger.producers.ProductionComponent;",
136             "",
137             "@ProductionComponent(modules = RepeatedProducerModule.class)",
138             "interface Parent {",
139             "  Child child();",
140             "}");
141     JavaFileObject repeatedModule =
142         JavaFileObjects.forSourceLines(
143             "test.RepeatedProducerModule",
144             "package test;",
145             "",
146             "import dagger.producers.ProducerModule;",
147             "",
148             "@ProducerModule",
149             "interface RepeatedProducerModule {}");
150     JavaFileObject subcomponent =
151         JavaFileObjects.forSourceLines(
152             "test.Child",
153             "package test;",
154             "",
155             "import dagger.producers.ProductionSubcomponent;",
156             "",
157             "@ProductionSubcomponent(modules = RepeatedProducerModule.class)",
158             "interface Child {}");
159     Compilation compilation = daggerCompiler().compile(component, subcomponent, repeatedModule);
160     assertThat(compilation).failed();
161     assertThat(compilation)
162         .hadErrorContaining(
163             message(
164                 "test.Child repeats @ProducerModules:",
165                 "  test.Parent also installs: test.RepeatedProducerModule"))
166         .inFile(component)
167         .onLineContaining("interface Parent");
168   }
169 
170   @Test
factoryMethodForSubcomponentWithBuilder_isNotAllowed()171   public void factoryMethodForSubcomponentWithBuilder_isNotAllowed() {
172     JavaFileObject module =
173         JavaFileObjects.forSourceLines(
174             "test.TestModule",
175             "package test;",
176             "",
177             "import dagger.Module;",
178             "import dagger.Provides;",
179             "",
180             "@Module(subcomponents = Sub.class)",
181             "class TestModule {",
182             "}");
183 
184     JavaFileObject subcomponent =
185         JavaFileObjects.forSourceLines(
186             "test.Sub",
187             "package test;",
188             "",
189             "import dagger.Subcomponent;",
190             "",
191             "@Subcomponent",
192             "interface Sub {",
193             "  @Subcomponent.Builder",
194             "  interface Builder {",
195             "    Sub build();",
196             "  }",
197             "}");
198 
199     JavaFileObject component =
200         JavaFileObjects.forSourceLines(
201             "test.Sub",
202             "package test;",
203             "",
204             "import dagger.Component;",
205             "",
206             "@Component(modules = TestModule.class)",
207             "interface C {",
208             "  Sub newSub();",
209             "}");
210 
211     Compilation compilation = daggerCompiler().compile(module, component, subcomponent);
212     assertThat(compilation).failed();
213     assertThat(compilation)
214         .hadErrorContaining(
215             "Components may not have factory methods for subcomponents that define a builder.");
216   }
217 
218   @Test
repeatedModulesWithScopes()219   public void repeatedModulesWithScopes() {
220     JavaFileObject testScope =
221         JavaFileObjects.forSourceLines(
222             "test.TestScope",
223             "package test;",
224             "",
225             "import javax.inject.Scope;",
226             "",
227             "@Scope",
228             "@interface TestScope {}");
229     JavaFileObject moduleWithScopedProvides =
230         JavaFileObjects.forSourceLines(
231             "test.ModuleWithScopedProvides",
232             "package test;",
233             "",
234             "import dagger.Module;",
235             "import dagger.Provides;",
236             "",
237             "@Module",
238             "class ModuleWithScopedProvides {",
239             "  @Provides",
240             "  @TestScope",
241             "  static Object o() { return new Object(); }",
242             "}");
243     JavaFileObject moduleWithScopedBinds =
244         JavaFileObjects.forSourceLines(
245             "test.ModuleWithScopedBinds",
246             "package test;",
247             "",
248             "import dagger.Binds;",
249             "import dagger.Module;",
250             "",
251             "@Module",
252             "interface ModuleWithScopedBinds {",
253             "  @Binds",
254             "  @TestScope",
255             "  Object o(String s);",
256             "}");
257     JavaFileObject parent =
258         JavaFileObjects.forSourceLines(
259             "test.Parent",
260             "package test;",
261             "",
262             "import dagger.Component;",
263             "",
264             "@Component(modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
265             "interface Parent {",
266             "  Child child();",
267             "}");
268     JavaFileObject child =
269         JavaFileObjects.forSourceLines(
270             "test.Child",
271             "package test;",
272             "",
273             "import dagger.Subcomponent;",
274             "",
275             "@Subcomponent(",
276             "    modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
277             "interface Child {}");
278     Compilation compilation =
279         daggerCompiler()
280             .compile(testScope, moduleWithScopedProvides, moduleWithScopedBinds, parent, child);
281     assertThat(compilation).failed();
282     assertThat(compilation)
283         .hadErrorContaining(
284             message(
285                 "test.Child repeats modules with scoped bindings or declarations:",
286                 "  - test.Parent also includes:",
287                 "    - test.ModuleWithScopedProvides with scopes: @test.TestScope",
288                 "    - test.ModuleWithScopedBinds with scopes: @test.TestScope"));
289   }
290 
291   @Test
repeatedModulesWithReusableScope()292   public void repeatedModulesWithReusableScope() {
293     JavaFileObject moduleWithScopedProvides =
294         JavaFileObjects.forSourceLines(
295             "test.ModuleWithScopedProvides",
296             "package test;",
297             "",
298             "import dagger.Module;",
299             "import dagger.Provides;",
300             "import dagger.Reusable;",
301             "",
302             "@Module",
303             "class ModuleWithScopedProvides {",
304             "  @Provides",
305             "  @Reusable",
306             "  static Object o() { return new Object(); }",
307             "}");
308     JavaFileObject moduleWithScopedBinds =
309         JavaFileObjects.forSourceLines(
310             "test.ModuleWithScopedBinds",
311             "package test;",
312             "",
313             "import dagger.Binds;",
314             "import dagger.Module;",
315             "import dagger.Reusable;",
316             "",
317             "@Module",
318             "interface ModuleWithScopedBinds {",
319             "  @Binds",
320             "  @Reusable",
321             "  Object o(String s);",
322             "}");
323     JavaFileObject parent =
324         JavaFileObjects.forSourceLines(
325             "test.Parent",
326             "package test;",
327             "",
328             "import dagger.Component;",
329             "",
330             "@Component(modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
331             "interface Parent {",
332             "  Child child();",
333             "}");
334     JavaFileObject child =
335         JavaFileObjects.forSourceLines(
336             "test.Child",
337             "package test;",
338             "",
339             "import dagger.Subcomponent;",
340             "",
341             "@Subcomponent(",
342             "    modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
343             "interface Child {}");
344     Compilation compilation =
345         daggerCompiler()
346             .compile(moduleWithScopedProvides, moduleWithScopedBinds, parent, child);
347     assertThat(compilation).succeededWithoutWarnings();
348   }
349 }
350