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 androidx.room.compiler.processing.util.Source;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.ImmutableMap;
22 import dagger.testing.compile.CompilerTests;
23 import org.junit.Test;
24 import org.junit.runner.RunWith;
25 import org.junit.runners.Parameterized;
26 import org.junit.runners.Parameterized.Parameters;
27 
28 /** Tests for {ComponentHierarchyValidator}. */
29 @RunWith(Parameterized.class)
30 public class ComponentHierarchyValidationTest {
31   @Parameters(name = "{0}")
parameters()32   public static ImmutableList<Object[]> parameters() {
33     return CompilerMode.TEST_PARAMETERS;
34   }
35 
36   private final CompilerMode compilerMode;
37 
ComponentHierarchyValidationTest(CompilerMode compilerMode)38   public ComponentHierarchyValidationTest(CompilerMode compilerMode) {
39     this.compilerMode = compilerMode;
40   }
41 
42   @Test
singletonSubcomponent()43   public void singletonSubcomponent() {
44     Source component =
45         CompilerTests.javaSource(
46             "test.Parent",
47             "package test;",
48             "",
49             "import dagger.Component;",
50             "import javax.inject.Singleton;",
51             "",
52             "@Singleton",
53             "@Component",
54             "interface Parent {",
55             "  Child child();",
56             "}");
57     Source subcomponent =
58         CompilerTests.javaSource(
59             "test.Child",
60             "package test;",
61             "",
62             "import dagger.Subcomponent;",
63             "import javax.inject.Singleton;",
64             "",
65             "@Singleton",
66             "@Subcomponent",
67             "interface Child {}");
68 
69     CompilerTests.daggerCompiler(component, subcomponent)
70         .withProcessingOptions(compilerMode.processorOptions())
71         .compile(
72             subject -> {
73               subject.hasErrorCount(1);
74               subject.hasErrorContaining(
75                   String.join(
76                       "\n",
77                       "test.Child has conflicting scopes:",
78                       "    test.Parent also has @Singleton"));
79             });
80 
81     // Check that compiling with disableInterComponentScopeValidation=none flag succeeds.
82     CompilerTests.daggerCompiler(component, subcomponent)
83         .withProcessingOptions(
84             ImmutableMap.<String, String>builder()
85                 .putAll(compilerMode.processorOptions())
86                 .put("dagger.disableInterComponentScopeValidation", "none")
87                 .buildOrThrow())
88         .compile(subject -> subject.hasErrorCount(0));
89   }
90 
91   @Test
productionComponents_productionScopeImplicitOnBoth()92   public void productionComponents_productionScopeImplicitOnBoth() {
93     Source component =
94         CompilerTests.javaSource(
95             "test.Parent",
96             "package test;",
97             "",
98             "import dagger.producers.ProductionComponent;",
99             "",
100             "@ProductionComponent(modules = ParentModule.class)",
101             "interface Parent {",
102             "  Child child();",
103             "  Object productionScopedObject();",
104             "}");
105     Source parentModule =
106         CompilerTests.javaSource(
107             "test.ParentModule",
108             "package test;",
109             "",
110             "import dagger.Provides;",
111             "import dagger.producers.ProducerModule;",
112             "import dagger.producers.ProductionScope;",
113             "",
114             "@ProducerModule",
115             "class ParentModule {",
116             "  @Provides @ProductionScope Object parentScopedObject() { return new Object(); }",
117             "}");
118     Source subcomponent =
119         CompilerTests.javaSource(
120             "test.Child",
121             "package test;",
122             "",
123             "import dagger.producers.ProductionSubcomponent;",
124             "",
125             "@ProductionSubcomponent(modules = ChildModule.class)",
126             "interface Child {",
127             "  String productionScopedString();",
128             "}");
129     Source childModule =
130         CompilerTests.javaSource(
131             "test.ChildModule",
132             "package test;",
133             "",
134             "import dagger.Provides;",
135             "import dagger.producers.ProducerModule;",
136             "import dagger.producers.ProductionScope;",
137             "",
138             "@ProducerModule",
139             "class ChildModule {",
140             "  @Provides @ProductionScope String childScopedString() { return new String(); }",
141             "}");
142     CompilerTests.daggerCompiler(component, subcomponent, parentModule, childModule)
143         .withProcessingOptions(compilerMode.processorOptions())
144         .compile(subject -> subject.hasErrorCount(0));
145   }
146 
147   @Test
producerModuleRepeated()148   public void producerModuleRepeated() {
149     Source component =
150         CompilerTests.javaSource(
151             "test.Parent",
152             "package test;",
153             "",
154             "import dagger.producers.ProductionComponent;",
155             "",
156             "@ProductionComponent(modules = RepeatedProducerModule.class)",
157             "interface Parent {",
158             "  Child child();",
159             "}");
160     Source repeatedModule =
161         CompilerTests.javaSource(
162             "test.RepeatedProducerModule",
163             "package test;",
164             "",
165             "import dagger.producers.ProducerModule;",
166             "",
167             "@ProducerModule",
168             "interface RepeatedProducerModule {}");
169     Source subcomponent =
170         CompilerTests.javaSource(
171             "test.Child",
172             "package test;",
173             "",
174             "import dagger.producers.ProductionSubcomponent;",
175             "",
176             "@ProductionSubcomponent(modules = RepeatedProducerModule.class)",
177             "interface Child {}");
178     CompilerTests.daggerCompiler(component, subcomponent, repeatedModule)
179         .withProcessingOptions(compilerMode.processorOptions())
180         .compile(
181             subject -> {
182               subject.hasErrorCount(1);
183               subject.hasErrorContaining(
184                       String.join(
185                           "\n",
186                           "test.Child repeats @ProducerModules:",
187                           "test.Parent also installs: test.RepeatedProducerModule"))
188                   .onSource(component)
189                   .onLineContaining("interface Parent");
190             });
191   }
192 
193   @Test
factoryMethodForSubcomponentWithBuilder_isNotAllowed()194   public void factoryMethodForSubcomponentWithBuilder_isNotAllowed() {
195     Source module =
196         CompilerTests.javaSource(
197             "test.TestModule",
198             "package test;",
199             "",
200             "import dagger.Module;",
201             "import dagger.Provides;",
202             "",
203             "@Module(subcomponents = Sub.class)",
204             "class TestModule {",
205             "}");
206 
207     Source subcomponent =
208         CompilerTests.javaSource(
209             "test.Sub",
210             "package test;",
211             "",
212             "import dagger.Subcomponent;",
213             "",
214             "@Subcomponent",
215             "interface Sub {",
216             "  @Subcomponent.Builder",
217             "  interface Builder {",
218             "    Sub build();",
219             "  }",
220             "}");
221 
222     Source component =
223         CompilerTests.javaSource(
224             "test.C",
225             "package test;",
226             "",
227             "import dagger.Component;",
228             "",
229             "@Component(modules = TestModule.class)",
230             "interface C {",
231             "  Sub newSub();",
232             "}");
233 
234     CompilerTests.daggerCompiler(module, component, subcomponent)
235         .withProcessingOptions(compilerMode.processorOptions())
236         .compile(
237             subject -> {
238               subject.hasErrorCount(1);
239               subject.hasErrorContaining(
240                   "Components may not have factory methods for subcomponents that define a "
241                       + "builder.");
242             });
243   }
244 
245   @Test
repeatedModulesWithScopes()246   public void repeatedModulesWithScopes() {
247     Source testScope =
248         CompilerTests.javaSource(
249             "test.TestScope",
250             "package test;",
251             "",
252             "import javax.inject.Scope;",
253             "",
254             "@Scope",
255             "@interface TestScope {}");
256     Source moduleWithScopedProvides =
257         CompilerTests.javaSource(
258             "test.ModuleWithScopedProvides",
259             "package test;",
260             "",
261             "import dagger.Module;",
262             "import dagger.Provides;",
263             "",
264             "@Module",
265             "class ModuleWithScopedProvides {",
266             "  @Provides",
267             "  @TestScope",
268             "  static Object o() { return new Object(); }",
269             "}");
270     Source moduleWithScopedBinds =
271         CompilerTests.javaSource(
272             "test.ModuleWithScopedBinds",
273             "package test;",
274             "",
275             "import dagger.Binds;",
276             "import dagger.Module;",
277             "",
278             "@Module",
279             "interface ModuleWithScopedBinds {",
280             "  @Binds",
281             "  @TestScope",
282             "  Object o(String s);",
283             "}");
284     Source parent =
285         CompilerTests.javaSource(
286             "test.Parent",
287             "package test;",
288             "",
289             "import dagger.Component;",
290             "",
291             "@Component(modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
292             "interface Parent {",
293             "  Child child();",
294             "}");
295     Source child =
296         CompilerTests.javaSource(
297             "test.Child",
298             "package test;",
299             "",
300             "import dagger.Subcomponent;",
301             "",
302             "@Subcomponent(",
303             "    modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
304             "interface Child {}");
305     CompilerTests.daggerCompiler(
306             testScope, moduleWithScopedProvides, moduleWithScopedBinds, parent, child)
307         .withProcessingOptions(compilerMode.processorOptions())
308         .compile(
309             subject -> {
310               subject.hasErrorCount(1);
311               subject.hasErrorContaining(
312                   String.join(
313                       "\n",
314                       "test.Child repeats modules with scoped bindings or declarations:",
315                       "- test.Parent also includes:",
316                       "    - test.ModuleWithScopedProvides with scopes: @test.TestScope",
317                       "    - test.ModuleWithScopedBinds with scopes: @test.TestScope"));
318             });
319   }
320 
321   @Test
repeatedModulesWithReusableScope()322   public void repeatedModulesWithReusableScope() {
323     Source moduleWithScopedProvides =
324         CompilerTests.javaSource(
325             "test.ModuleWithScopedProvides",
326             "package test;",
327             "",
328             "import dagger.Module;",
329             "import dagger.Provides;",
330             "import dagger.Reusable;",
331             "",
332             "@Module",
333             "class ModuleWithScopedProvides {",
334             "  @Provides",
335             "  @Reusable",
336             "  static Object o() { return new Object(); }",
337             "}");
338     Source moduleWithScopedBinds =
339         CompilerTests.javaSource(
340             "test.ModuleWithScopedBinds",
341             "package test;",
342             "",
343             "import dagger.Binds;",
344             "import dagger.Module;",
345             "import dagger.Reusable;",
346             "",
347             "@Module",
348             "interface ModuleWithScopedBinds {",
349             "  @Binds",
350             "  @Reusable",
351             "  Object o(String s);",
352             "}");
353     Source parent =
354         CompilerTests.javaSource(
355             "test.Parent",
356             "package test;",
357             "",
358             "import dagger.Component;",
359             "",
360             "@Component(modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
361             "interface Parent {",
362             "  Child child();",
363             "}");
364     Source child =
365         CompilerTests.javaSource(
366             "test.Child",
367             "package test;",
368             "",
369             "import dagger.Subcomponent;",
370             "",
371             "@Subcomponent(",
372             "    modules = {ModuleWithScopedProvides.class, ModuleWithScopedBinds.class})",
373             "interface Child {}");
374     CompilerTests.daggerCompiler(moduleWithScopedProvides, moduleWithScopedBinds, parent, child)
375         .withProcessingOptions(compilerMode.processorOptions())
376         .compile(
377             subject -> {
378               subject.hasErrorCount(0);
379               subject.hasWarningCount(0);
380             });
381   }
382 }
383