• 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 
20 import androidx.room.compiler.processing.util.Source;
21 import com.google.common.collect.ImmutableMap;
22 import dagger.testing.compile.CompilerTests;
23 import dagger.testing.golden.GoldenFileRule;
24 import java.util.Collection;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.Parameterized;
29 import org.junit.runners.Parameterized.Parameters;
30 
31 @RunWith(Parameterized.class)
32 public class ProductionComponentProcessorTest {
33   @Parameters(name = "{0}")
parameters()34   public static Collection<Object[]> parameters() {
35     return CompilerMode.TEST_PARAMETERS;
36   }
37 
38   private static final Source EXECUTOR_MODULE =
39       CompilerTests.javaSource(
40           "test.ExecutorModule",
41           "package test;",
42           "",
43           "import com.google.common.util.concurrent.MoreExecutors;",
44           "import dagger.Module;",
45           "import dagger.Provides;",
46           "import dagger.producers.Production;",
47           "import java.util.concurrent.Executor;",
48           "",
49           "@Module",
50           "final class ExecutorModule {",
51           "  @Provides @Production Executor executor() {",
52           "    return MoreExecutors.directExecutor();",
53           "  }",
54           "}");
55 
56   @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
57 
58   private final CompilerMode compilerMode;
59 
ProductionComponentProcessorTest(CompilerMode compilerMode)60   public ProductionComponentProcessorTest(CompilerMode compilerMode) {
61     this.compilerMode = compilerMode;
62   }
63 
componentOnConcreteClass()64   @Test public void componentOnConcreteClass() {
65     Source componentFile =
66         CompilerTests.javaSource("test.NotAComponent",
67         "package test;",
68         "",
69         "import dagger.producers.ProductionComponent;",
70         "",
71         "@ProductionComponent",
72         "final class NotAComponent {}");
73     CompilerTests.daggerCompiler(componentFile)
74         .withProcessingOptions(compilerMode.processorOptions())
75         .compile(
76             subject -> {
77               subject.hasErrorCount(1);
78               subject.hasErrorContaining(
79                   "@ProductionComponent may only be applied to an interface or abstract class");
80             });
81   }
82 
componentOnEnum()83   @Test public void componentOnEnum() {
84     Source componentFile =
85         CompilerTests.javaSource("test.NotAComponent",
86         "package test;",
87         "",
88         "import dagger.producers.ProductionComponent;",
89         "",
90         "@ProductionComponent",
91         "enum NotAComponent {",
92         "  INSTANCE",
93         "}");
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 
componentOnAnnotation()104   @Test public void componentOnAnnotation() {
105     Source componentFile =
106         CompilerTests.javaSource("test.NotAComponent",
107         "package test;",
108         "",
109         "import dagger.producers.ProductionComponent;",
110         "",
111         "@ProductionComponent",
112         "@interface NotAComponent {}");
113     CompilerTests.daggerCompiler(componentFile)
114         .withProcessingOptions(compilerMode.processorOptions())
115         .compile(
116             subject -> {
117               subject.hasErrorCount(1);
118               subject.hasErrorContaining(
119                   "@ProductionComponent may only be applied to an interface or abstract class");
120             });
121   }
122 
nonModuleModule()123   @Test public void nonModuleModule() {
124     Source componentFile =
125         CompilerTests.javaSource("test.NotAComponent",
126         "package test;",
127         "",
128         "import dagger.producers.ProductionComponent;",
129         "",
130         "@ProductionComponent(modules = Object.class)",
131         "interface NotAComponent {}");
132     CompilerTests.daggerCompiler(componentFile)
133         .withProcessingOptions(compilerMode.processorOptions())
134         .compile(
135             subject -> {
136               subject.hasErrorCount(1);
137               subject.hasErrorContaining("is not annotated with one of @Module, @ProducerModule");
138             });
139   }
140 
141   @Test
dependsOnProductionExecutor()142   public void dependsOnProductionExecutor() throws Exception {
143     Source producerModuleFile =
144         CompilerTests.javaSource(
145             "test.SimpleModule",
146             "package test;",
147             "",
148             "import dagger.producers.ProducerModule;",
149             "import dagger.producers.Produces;",
150             "import dagger.producers.Production;",
151             "import java.util.concurrent.Executor;",
152             "",
153             "@ProducerModule",
154             "final class SimpleModule {",
155             "  @Produces String str(@Production Executor executor) {",
156             "    return \"\";",
157             "  }",
158             "}");
159     Source componentFile =
160         CompilerTests.javaSource(
161             "test.SimpleComponent",
162             "package test;",
163             "",
164             "import com.google.common.util.concurrent.ListenableFuture;",
165             "import dagger.producers.ProductionComponent;",
166             "import java.util.concurrent.Executor;",
167             "",
168             "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})",
169             "interface SimpleComponent {",
170             "  ListenableFuture<String> str();",
171             "",
172             "  @ProductionComponent.Builder",
173             "  interface Builder {",
174             "    SimpleComponent build();",
175             "  }",
176             "}");
177 
178     String errorMessage = "String may not depend on the production executor";
179     CompilerTests.daggerCompiler(EXECUTOR_MODULE, producerModuleFile, componentFile)
180         .withProcessingOptions(compilerMode.processorOptions())
181         .compile(
182             subject -> {
183               subject.hasErrorCount(1);
184               subject.hasErrorContaining(errorMessage)
185                   .onSource(componentFile)
186                   .onLineContaining("interface SimpleComponent");
187             });
188 
189     // Verify that the error is reported on the module when fullBindingGraphValidation is enabled.
190     CompilerTests.daggerCompiler(producerModuleFile)
191         .withProcessingOptions(
192             ImmutableMap.<String, String>builder()
193                 .putAll(compilerMode.processorOptions())
194                 .put("dagger.fullBindingGraphValidation", "ERROR")
195                 .buildOrThrow())
196         .compile(
197             subject -> {
198               subject.hasErrorCount(1);
199               subject.hasErrorContaining(errorMessage)
200                   .onSource(producerModuleFile)
201                   .onLineContaining("class SimpleModule");
202             });
203     // TODO(dpb): Report at the binding if enclosed in the module.
204   }
205 
206   @Test
dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs()207   public void dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs() throws Exception {
208     Source myComponent =
209         CompilerTests.javaSource(
210             "test.MyComponent",
211             "package test;",
212             "",
213             "import dagger.Component;",
214             "",
215             "@Component(modules = MyModule.class)",
216             "interface MyComponent {}");
217     Source myModule =
218         CompilerTests.javaSource(
219             "test.MyModule",
220             "package test;",
221             "",
222             "import dagger.Component;",
223             "import dagger.Module;",
224             "",
225             "@Module(subcomponents = MyProductionSubcomponent.class)",
226             "interface MyModule {}");
227     Source myProductionSubcomponent =
228         CompilerTests.javaSource(
229             "test.MyProductionSubcomponent",
230             "package test;",
231             "",
232             "import dagger.producers.ProductionSubcomponent;",
233             "",
234             "@ProductionSubcomponent",
235             "interface MyProductionSubcomponent {",
236             "  @ProductionSubcomponent.Builder",
237             "  interface Builder {",
238             "    MyProductionSubcomponent build();",
239             "  }",
240             "}");
241 
242     CompilerTests.daggerCompiler(myComponent, myModule, myProductionSubcomponent)
243         .withProcessingOptions(
244             ImmutableMap.<String, String>builder()
245                 .putAll(compilerMode.processorOptions())
246                 .put("dagger.pluginsVisitFullBindingGraphs", "ENABLED")
247                 .buildOrThrow())
248         .compile(subject -> subject.hasErrorCount(0));
249   }
250 
251   @Test
simpleComponent()252   public void simpleComponent() throws Exception {
253     Source component =
254         CompilerTests.javaSource(
255             "test.TestClass",
256             "package test;",
257             "",
258             "import com.google.common.util.concurrent.ListenableFuture;",
259             "import com.google.common.util.concurrent.MoreExecutors;",
260             "import dagger.Module;",
261             "import dagger.Provides;",
262             "import dagger.producers.ProducerModule;",
263             "import dagger.producers.Produces;",
264             "import dagger.producers.Production;",
265             "import dagger.producers.ProductionComponent;",
266             "import java.util.concurrent.Executor;",
267             "import javax.inject.Inject;",
268             "",
269             "final class TestClass {",
270             "  static final class C {",
271             "    @Inject C() {}",
272             "  }",
273             "",
274             "  interface A {}",
275             "  interface B {}",
276             "",
277             "  @Module",
278             "  static final class BModule {",
279             "    @Provides B b(C c) {",
280             "      return null;",
281             "    }",
282             "",
283             "    @Provides @Production Executor executor() {",
284             "      return MoreExecutors.directExecutor();",
285             "    }",
286             "  }",
287             "",
288             "  @ProducerModule",
289             "  static final class AModule {",
290             "    @Produces ListenableFuture<A> a(B b) {",
291             "      return null;",
292             "    }",
293             "  }",
294             "",
295             "  @ProductionComponent(modules = {AModule.class, BModule.class})",
296             "  interface SimpleComponent {",
297             "    ListenableFuture<A> a();",
298             "  }",
299             "}");
300 
301     CompilerTests.daggerCompiler(component)
302         .withProcessingOptions(compilerMode.processorOptions())
303         .compile(
304             subject -> {
305               subject.hasErrorCount(0);
306               subject.generatedSource(
307                   goldenFileRule.goldenSource("test/DaggerTestClass_SimpleComponent"));
308             });
309   }
310 
nullableProducersAreNotErrors()311   @Test public void nullableProducersAreNotErrors() {
312     Source component =
313         CompilerTests.javaSource("test.TestClass",
314         "package test;",
315         "",
316         "import com.google.common.util.concurrent.ListenableFuture;",
317         "import com.google.common.util.concurrent.MoreExecutors;",
318         "import dagger.Module;",
319         "import dagger.Provides;",
320         "import dagger.producers.ProducerModule;",
321         "import dagger.producers.Produces;",
322         "import dagger.producers.Production;",
323         "import dagger.producers.ProductionComponent;",
324         "import java.util.concurrent.Executor;",
325         "import javax.annotation.Nullable;",
326         "import javax.inject.Inject;",
327         "",
328         "final class TestClass {",
329         "  interface A {}",
330         "  interface B {}",
331         "  interface C {}",
332         "",
333         "  @Module",
334         "  static final class CModule {",
335         "    @Provides @Nullable C c() {",
336         "      return null;",
337         "    }",
338         "",
339         "    @Provides @Production Executor executor() {",
340         "      return MoreExecutors.directExecutor();",
341         "    }",
342         "  }",
343         "",
344         "  @ProducerModule",
345         "  static final class ABModule {",
346         "    @Produces @Nullable B b(@Nullable C c) {",
347         "      return null;",
348         "    }",
349 
350         "    @Produces @Nullable ListenableFuture<A> a(B b) {",  // NOTE: B not injected as nullable
351         "      return null;",
352         "    }",
353         "  }",
354         "",
355         "  @ProductionComponent(modules = {ABModule.class, CModule.class})",
356         "  interface SimpleComponent {",
357         "    ListenableFuture<A> a();",
358         "  }",
359         "}");
360     CompilerTests.daggerCompiler(component)
361         .withProcessingOptions(compilerMode.processorOptions())
362         .compile(
363             subject -> {
364               subject.hasErrorCount(0);
365               subject.hasWarningCount(2);
366               subject.hasWarningContaining("@Nullable on @Produces methods does not do anything")
367                   .onSource(component)
368                   .onLine(33);
369               subject.hasWarningContaining("@Nullable on @Produces methods does not do anything")
370                   .onSource(component)
371                   .onLine(36);
372             });
373   }
374 
375   @Test
productionScope_injectConstructor()376   public void productionScope_injectConstructor() throws Exception {
377     Source productionScoped =
378         CompilerTests.javaSource(
379             "test.ProductionScoped",
380             "package test;",
381             "",
382             "import dagger.producers.ProductionScope;",
383             "import javax.inject.Inject;",
384             "",
385             "@ProductionScope",
386             "class ProductionScoped {",
387             "  @Inject ProductionScoped() {}",
388             "}");
389     Source parent =
390         CompilerTests.javaSource(
391             "test.Parent",
392             "package test;",
393             "",
394             "import dagger.producers.ProductionComponent;",
395             "",
396             "@ProductionComponent",
397             "interface Parent {",
398             "  Child child();",
399             "}");
400     Source child =
401         CompilerTests.javaSource(
402             "test.Child",
403             "package test;",
404             "",
405             "import dagger.producers.ProductionSubcomponent;",
406             "",
407             "@ProductionSubcomponent",
408             "interface Child {",
409             "  ProductionScoped productionScoped();",
410             "}");
411 
412     CompilerTests.daggerCompiler(productionScoped, parent, child)
413         .withProcessingOptions(compilerMode.processorOptions())
414         .compile(
415             subject -> {
416               subject.hasErrorCount(0);
417               subject.generatedSource(goldenFileRule.goldenSource("test/DaggerParent"));
418             });
419   }
420 
421   @Test
requestProducerNodeWithProvider_failsWithNotSupportedError()422   public void requestProducerNodeWithProvider_failsWithNotSupportedError() {
423     Source producerModuleFile =
424         CompilerTests.javaSource(
425             "test.SimpleModule",
426             "package test;",
427             "",
428             "import dagger.producers.ProducerModule;",
429             "import dagger.producers.Produces;",
430             "import javax.inject.Provider;",
431             "import java.util.concurrent.Executor;",
432             "import dagger.producers.Production;",
433             "",
434             "@ProducerModule",
435             "final class SimpleModule {",
436             "  @Produces String str(Provider<Integer> num) {",
437             "    return \"\";",
438             "  }",
439             "  @Produces Integer num() { return 1; }",
440             "}");
441     Source componentFile =
442         CompilerTests.javaSource(
443             "test.SimpleComponent",
444             "package test;",
445             "",
446             "import com.google.common.util.concurrent.ListenableFuture;",
447             "import dagger.producers.ProductionComponent;",
448             "",
449             "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})",
450             "interface SimpleComponent {",
451             "  ListenableFuture<String> str();",
452             "",
453             "  @ProductionComponent.Builder",
454             "  interface Builder {",
455             "    SimpleComponent build();",
456             "  }",
457             "}");
458 
459     CompilerTests.daggerCompiler(EXECUTOR_MODULE, producerModuleFile, componentFile)
460         .withProcessingOptions(compilerMode.processorOptions())
461         .compile(
462             subject -> {
463               subject.hasErrorCount(1);
464               subject.hasErrorContaining(
465                   "request kind PROVIDER cannot be satisfied by production binding");
466             });
467   }
468 
469   @Test
productionBindingKind_failsIfScoped()470   public void productionBindingKind_failsIfScoped() {
471     Source component =
472         CompilerTests.javaSource(
473             "test.TestComponent",
474             "package test;",
475             "",
476             "import com.google.common.util.concurrent.ListenableFuture;",
477             "import dagger.producers.ProductionComponent;",
478             "",
479             "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
480             "interface TestComponent {",
481             "  ListenableFuture<String> str();",
482             "}");
483     Source module =
484         CompilerTests.javaSource(
485             "test.TestModule",
486             "package test;",
487             "",
488             "import dagger.producers.ProducerModule;",
489             "import dagger.producers.Produces;",
490             "import dagger.producers.ProductionScope;",
491             "import javax.inject.Provider;",
492             "import java.util.concurrent.Executor;",
493             "import dagger.producers.Production;",
494             "",
495             "@ProducerModule",
496             "interface TestModule {",
497             "  @ProductionScope",
498             "  @Produces",
499             "  static String provideString() { return \"\"; }",
500             "}");
501 
502     CompilerTests.daggerCompiler(component, module, EXECUTOR_MODULE)
503         .withProcessingOptions(compilerMode.processorOptions())
504         .compile(
505             subject -> {
506               subject.hasErrorCount(1);
507               subject.hasErrorContaining("@Produces methods cannot be scoped");
508             });
509   }
510 
511   @Test
delegateToProductionBindingKind_failsIfScoped()512   public void delegateToProductionBindingKind_failsIfScoped() {
513     Source component =
514         CompilerTests.javaSource(
515             "test.TestComponent",
516             "package test;",
517             "",
518             "import com.google.common.util.concurrent.ListenableFuture;",
519             "import dagger.producers.ProductionComponent;",
520             "",
521             "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
522             "interface TestComponent {",
523             "  ListenableFuture<Foo> foo();",
524             "}");
525     Source module =
526         CompilerTests.javaSource(
527             "test.TestModule",
528             "package test;",
529             "",
530             "import dagger.Binds;",
531             "import dagger.producers.ProducerModule;",
532             "import dagger.producers.Produces;",
533             "import dagger.producers.ProductionScope;",
534             "import javax.inject.Provider;",
535             "import java.util.concurrent.Executor;",
536             "import dagger.producers.Production;",
537             "",
538             "@ProducerModule",
539             "interface TestModule {",
540             "  @ProductionScope",
541             "  @Binds",
542             "  Foo bind(FooImpl impl);",
543             "",
544             "  @Produces",
545             "  static FooImpl fooImpl() { return new FooImpl(); }",
546             "}");
547     Source foo =
548         CompilerTests.javaSource(
549             "test.Foo",
550             "package test;",
551             "",
552             "interface Foo {}");
553     Source fooImpl =
554         CompilerTests.javaSource(
555             "test.FooImpl",
556             "package test;",
557             "",
558             "final class FooImpl implements Foo {}");
559 
560     CompilerTests.daggerCompiler(component, module, foo, fooImpl, EXECUTOR_MODULE)
561         .withProcessingOptions(compilerMode.processorOptions())
562         .compile(
563             subject -> {
564               subject.hasErrorCount(1);
565               subject.hasErrorContaining(
566                   "@ProductionScope @Binds Foo TestModule.bind(FooImpl) cannot be scoped "
567                       + "because it delegates to an @Produces method");
568             });
569   }
570 
571   @Test
multipleDelegatesToProductionBindingKind_failsIfScoped()572   public void multipleDelegatesToProductionBindingKind_failsIfScoped() {
573     Source component =
574         CompilerTests.javaSource(
575             "test.TestComponent",
576             "package test;",
577             "",
578             "import com.google.common.util.concurrent.ListenableFuture;",
579             "import dagger.producers.ProductionComponent;",
580             "",
581             "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
582             "interface TestComponent {",
583             "  ListenableFuture<FooSuper> fooSuper();",
584             "}");
585     Source module =
586         CompilerTests.javaSource(
587             "test.TestModule",
588             "package test;",
589             "",
590             "import dagger.Binds;",
591             "import dagger.producers.ProducerModule;",
592             "import dagger.producers.Produces;",
593             "import dagger.producers.ProductionScope;",
594             "import javax.inject.Provider;",
595             "import java.util.concurrent.Executor;",
596             "import dagger.producers.Production;",
597             "",
598             "@ProducerModule",
599             "interface TestModule {",
600             "  @ProductionScope",
601             "  @Binds",
602             "  FooSuper bindFooSuper(Foo impl);",
603             "",
604             "  @Binds",
605             "  Foo bindFoo(FooImpl impl);",
606             "",
607             "  @Produces",
608             "  static FooImpl fooImpl() { return new FooImpl(); }",
609             "}");
610     Source fooSuper =
611         CompilerTests.javaSource(
612             "test.FooSuper",
613             "package test;",
614             "",
615             "interface FooSuper {}");
616     Source foo =
617         CompilerTests.javaSource(
618             "test.Foo",
619             "package test;",
620             "",
621             "interface Foo extends FooSuper {}");
622     Source fooImpl =
623         CompilerTests.javaSource(
624             "test.FooImpl",
625             "package test;",
626             "",
627             "final class FooImpl implements Foo {}");
628 
629     CompilerTests.daggerCompiler(component, module, fooSuper, foo, fooImpl, EXECUTOR_MODULE)
630         .withProcessingOptions(compilerMode.processorOptions())
631         .compile(
632             subject -> {
633               subject.hasErrorCount(1);
634               subject.hasErrorContaining(
635                   "@ProductionScope @Binds FooSuper TestModule.bindFooSuper(Foo) cannot be scoped "
636                       + "because it delegates to an @Produces method");
637             });
638   }
639 }
640