• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.room.compiler.processing.util.Source;
20 import com.google.common.collect.ImmutableMap;
21 import dagger.testing.compile.CompilerTests;
22 import dagger.testing.golden.GoldenFileRule;
23 import java.util.Collection;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.junit.runners.Parameterized;
28 import org.junit.runners.Parameterized.Parameters;
29 
30 @RunWith(Parameterized.class)
31 public class ProductionComponentProcessorTest {
32   @Parameters(name = "{0}")
parameters()33   public static Collection<Object[]> parameters() {
34     return CompilerMode.TEST_PARAMETERS;
35   }
36 
37   @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
38 
39   private final CompilerMode compilerMode;
40 
ProductionComponentProcessorTest(CompilerMode compilerMode)41   public ProductionComponentProcessorTest(CompilerMode compilerMode) {
42     this.compilerMode = compilerMode;
43   }
44 
componentOnConcreteClass()45   @Test public void componentOnConcreteClass() {
46     Source componentFile =
47         CompilerTests.javaSource("test.NotAComponent",
48         "package test;",
49         "",
50         "import dagger.producers.ProductionComponent;",
51         "",
52         "@ProductionComponent",
53         "final class NotAComponent {}");
54     CompilerTests.daggerCompiler(componentFile)
55         .withProcessingOptions(compilerMode.processorOptions())
56         .compile(
57             subject -> {
58               subject.hasErrorCount(1);
59               subject.hasErrorContaining(
60                   "@ProductionComponent may only be applied to an interface or abstract class");
61             });
62   }
63 
componentOnEnum()64   @Test public void componentOnEnum() {
65     Source componentFile =
66         CompilerTests.javaSource("test.NotAComponent",
67         "package test;",
68         "",
69         "import dagger.producers.ProductionComponent;",
70         "",
71         "@ProductionComponent",
72         "enum NotAComponent {",
73         "  INSTANCE",
74         "}");
75     CompilerTests.daggerCompiler(componentFile)
76         .withProcessingOptions(compilerMode.processorOptions())
77         .compile(
78             subject -> {
79               subject.hasErrorCount(1);
80               subject.hasErrorContaining(
81                   "@ProductionComponent may only be applied to an interface or abstract class");
82             });
83   }
84 
componentOnAnnotation()85   @Test public void componentOnAnnotation() {
86     Source componentFile =
87         CompilerTests.javaSource("test.NotAComponent",
88         "package test;",
89         "",
90         "import dagger.producers.ProductionComponent;",
91         "",
92         "@ProductionComponent",
93         "@interface NotAComponent {}");
94     CompilerTests.daggerCompiler(componentFile)
95         .withProcessingOptions(compilerMode.processorOptions())
96         .compile(
97             subject -> {
98               subject.hasErrorCount(1);
99               subject.hasErrorContaining(
100                   "@ProductionComponent may only be applied to an interface or abstract class");
101             });
102   }
103 
nonModuleModule()104   @Test public void nonModuleModule() {
105     Source componentFile =
106         CompilerTests.javaSource("test.NotAComponent",
107         "package test;",
108         "",
109         "import dagger.producers.ProductionComponent;",
110         "",
111         "@ProductionComponent(modules = Object.class)",
112         "interface NotAComponent {}");
113     CompilerTests.daggerCompiler(componentFile)
114         .withProcessingOptions(compilerMode.processorOptions())
115         .compile(
116             subject -> {
117               subject.hasErrorCount(1);
118               subject.hasErrorContaining("is not annotated with one of @Module, @ProducerModule");
119             });
120   }
121 
122   @Test
dependsOnProductionExecutor()123   public void dependsOnProductionExecutor() throws Exception {
124     Source moduleFile =
125         CompilerTests.javaSource(
126             "test.ExecutorModule",
127             "package test;",
128             "",
129             "import com.google.common.util.concurrent.MoreExecutors;",
130             "import dagger.Module;",
131             "import dagger.Provides;",
132             "import dagger.producers.Production;",
133             "import java.util.concurrent.Executor;",
134             "",
135             "@Module",
136             "final class ExecutorModule {",
137             "  @Provides @Production Executor executor() {",
138             "    return MoreExecutors.directExecutor();",
139             "  }",
140             "}");
141     Source producerModuleFile =
142         CompilerTests.javaSource(
143             "test.SimpleModule",
144             "package test;",
145             "",
146             "import dagger.producers.ProducerModule;",
147             "import dagger.producers.Produces;",
148             "import dagger.producers.Production;",
149             "import java.util.concurrent.Executor;",
150             "",
151             "@ProducerModule",
152             "final class SimpleModule {",
153             "  @Produces String str(@Production Executor executor) {",
154             "    return \"\";",
155             "  }",
156             "}");
157     Source componentFile =
158         CompilerTests.javaSource(
159             "test.SimpleComponent",
160             "package test;",
161             "",
162             "import com.google.common.util.concurrent.ListenableFuture;",
163             "import dagger.producers.ProductionComponent;",
164             "import java.util.concurrent.Executor;",
165             "",
166             "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})",
167             "interface SimpleComponent {",
168             "  ListenableFuture<String> str();",
169             "",
170             "  @ProductionComponent.Builder",
171             "  interface Builder {",
172             "    SimpleComponent build();",
173             "  }",
174             "}");
175 
176     String errorMessage = "String may not depend on the production executor";
177     CompilerTests.daggerCompiler(moduleFile, producerModuleFile, componentFile)
178         .withProcessingOptions(compilerMode.processorOptions())
179         .compile(
180             subject -> {
181               subject.hasErrorCount(1);
182               subject.hasErrorContaining(errorMessage)
183                   .onSource(componentFile)
184                   .onLineContaining("interface SimpleComponent");
185             });
186 
187     // Verify that the error is reported on the module when fullBindingGraphValidation is enabled.
188     CompilerTests.daggerCompiler(producerModuleFile)
189         .withProcessingOptions(
190             ImmutableMap.<String, String>builder()
191                 .putAll(compilerMode.processorOptions())
192                 .put("dagger.fullBindingGraphValidation", "ERROR")
193                 .buildOrThrow())
194         .compile(
195             subject -> {
196               subject.hasErrorCount(1);
197               subject.hasErrorContaining(errorMessage)
198                   .onSource(producerModuleFile)
199                   .onLineContaining("class SimpleModule");
200             });
201     // TODO(dpb): Report at the binding if enclosed in the module.
202   }
203 
204   @Test
dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs()205   public void dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs() throws Exception {
206     Source myComponent =
207         CompilerTests.javaSource(
208             "test.MyComponent",
209             "package test;",
210             "",
211             "import dagger.Component;",
212             "",
213             "@Component(modules = MyModule.class)",
214             "interface MyComponent {}");
215     Source myModule =
216         CompilerTests.javaSource(
217             "test.MyModule",
218             "package test;",
219             "",
220             "import dagger.Component;",
221             "import dagger.Module;",
222             "",
223             "@Module(subcomponents = MyProductionSubcomponent.class)",
224             "interface MyModule {}");
225     Source myProductionSubcomponent =
226         CompilerTests.javaSource(
227             "test.MyProductionSubcomponent",
228             "package test;",
229             "",
230             "import dagger.producers.ProductionSubcomponent;",
231             "",
232             "@ProductionSubcomponent",
233             "interface MyProductionSubcomponent {",
234             "  @ProductionSubcomponent.Builder",
235             "  interface Builder {",
236             "    MyProductionSubcomponent build();",
237             "  }",
238             "}");
239 
240     CompilerTests.daggerCompiler(myComponent, myModule, myProductionSubcomponent)
241         .withProcessingOptions(
242             ImmutableMap.<String, String>builder()
243                 .putAll(compilerMode.processorOptions())
244                 .put("dagger.pluginsVisitFullBindingGraphs", "ENABLED")
245                 .buildOrThrow())
246         .compile(subject -> subject.hasErrorCount(0));
247   }
248 
249   @Test
simpleComponent()250   public void simpleComponent() throws Exception {
251     Source component =
252         CompilerTests.javaSource(
253             "test.TestClass",
254             "package test;",
255             "",
256             "import com.google.common.util.concurrent.ListenableFuture;",
257             "import com.google.common.util.concurrent.MoreExecutors;",
258             "import dagger.Module;",
259             "import dagger.Provides;",
260             "import dagger.producers.ProducerModule;",
261             "import dagger.producers.Produces;",
262             "import dagger.producers.Production;",
263             "import dagger.producers.ProductionComponent;",
264             "import java.util.concurrent.Executor;",
265             "import javax.inject.Inject;",
266             "",
267             "final class TestClass {",
268             "  static final class C {",
269             "    @Inject C() {}",
270             "  }",
271             "",
272             "  interface A {}",
273             "  interface B {}",
274             "",
275             "  @Module",
276             "  static final class BModule {",
277             "    @Provides B b(C c) {",
278             "      return null;",
279             "    }",
280             "",
281             "    @Provides @Production Executor executor() {",
282             "      return MoreExecutors.directExecutor();",
283             "    }",
284             "  }",
285             "",
286             "  @ProducerModule",
287             "  static final class AModule {",
288             "    @Produces ListenableFuture<A> a(B b) {",
289             "      return null;",
290             "    }",
291             "  }",
292             "",
293             "  @ProductionComponent(modules = {AModule.class, BModule.class})",
294             "  interface SimpleComponent {",
295             "    ListenableFuture<A> a();",
296             "  }",
297             "}");
298 
299     CompilerTests.daggerCompiler(component)
300         .withProcessingOptions(compilerMode.processorOptions())
301         .compile(
302             subject -> {
303               subject.hasErrorCount(0);
304               subject.generatedSource(
305                   goldenFileRule.goldenSource("test/DaggerTestClass_SimpleComponent"));
306             });
307   }
308 
nullableProducersAreNotErrors()309   @Test public void nullableProducersAreNotErrors() {
310     Source component =
311         CompilerTests.javaSource("test.TestClass",
312         "package test;",
313         "",
314         "import com.google.common.util.concurrent.ListenableFuture;",
315         "import com.google.common.util.concurrent.MoreExecutors;",
316         "import dagger.Module;",
317         "import dagger.Provides;",
318         "import dagger.producers.ProducerModule;",
319         "import dagger.producers.Produces;",
320         "import dagger.producers.Production;",
321         "import dagger.producers.ProductionComponent;",
322         "import java.util.concurrent.Executor;",
323         "import javax.annotation.Nullable;",
324         "import javax.inject.Inject;",
325         "",
326         "final class TestClass {",
327         "  interface A {}",
328         "  interface B {}",
329         "  interface C {}",
330         "",
331         "  @Module",
332         "  static final class CModule {",
333         "    @Provides @Nullable C c() {",
334         "      return null;",
335         "    }",
336         "",
337         "    @Provides @Production Executor executor() {",
338         "      return MoreExecutors.directExecutor();",
339         "    }",
340         "  }",
341         "",
342         "  @ProducerModule",
343         "  static final class ABModule {",
344         "    @Produces @Nullable B b(@Nullable C c) {",
345         "      return null;",
346         "    }",
347 
348         "    @Produces @Nullable ListenableFuture<A> a(B b) {",  // NOTE: B not injected as nullable
349         "      return null;",
350         "    }",
351         "  }",
352         "",
353         "  @ProductionComponent(modules = {ABModule.class, CModule.class})",
354         "  interface SimpleComponent {",
355         "    ListenableFuture<A> a();",
356         "  }",
357         "}");
358     CompilerTests.daggerCompiler(component)
359         .withProcessingOptions(compilerMode.processorOptions())
360         .compile(
361             subject -> {
362               subject.hasErrorCount(0);
363               subject.hasWarningCount(2);
364               subject.hasWarningContaining("@Nullable on @Produces methods does not do anything")
365                   .onSource(component)
366                   .onLine(33);
367               subject.hasWarningContaining("@Nullable on @Produces methods does not do anything")
368                   .onSource(component)
369                   .onLine(36);
370             });
371   }
372 
373   @Test
productionScope_injectConstructor()374   public void productionScope_injectConstructor() throws Exception {
375     Source productionScoped =
376         CompilerTests.javaSource(
377             "test.ProductionScoped",
378             "package test;",
379             "",
380             "import dagger.producers.ProductionScope;",
381             "import javax.inject.Inject;",
382             "",
383             "@ProductionScope",
384             "class ProductionScoped {",
385             "  @Inject ProductionScoped() {}",
386             "}");
387     Source parent =
388         CompilerTests.javaSource(
389             "test.Parent",
390             "package test;",
391             "",
392             "import dagger.producers.ProductionComponent;",
393             "",
394             "@ProductionComponent",
395             "interface Parent {",
396             "  Child child();",
397             "}");
398     Source child =
399         CompilerTests.javaSource(
400             "test.Child",
401             "package test;",
402             "",
403             "import dagger.producers.ProductionSubcomponent;",
404             "",
405             "@ProductionSubcomponent",
406             "interface Child {",
407             "  ProductionScoped productionScoped();",
408             "}");
409 
410     CompilerTests.daggerCompiler(productionScoped, parent, child)
411         .withProcessingOptions(compilerMode.processorOptions())
412         .compile(
413             subject -> {
414               subject.hasErrorCount(0);
415               subject.generatedSource(goldenFileRule.goldenSource("test/DaggerParent"));
416             });
417   }
418 }
419