• 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.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 /** Producer-specific validation tests. */
29 @RunWith(Parameterized.class)
30 public class ProductionGraphValidationTest {
31   @Parameters(name = "{0}")
parameters()32   public static ImmutableList<Object[]> parameters() {
33     return CompilerMode.TEST_PARAMETERS;
34   }
35 
36   private final CompilerMode compilerMode;
37 
ProductionGraphValidationTest(CompilerMode compilerMode)38   public ProductionGraphValidationTest(CompilerMode compilerMode) {
39     this.compilerMode = compilerMode;
40   }
41 
42   private static final Source EXECUTOR_MODULE =
43       CompilerTests.javaSource(
44           "test.ExecutorModule",
45           "package test;",
46           "",
47           "import com.google.common.util.concurrent.MoreExecutors;",
48           "import dagger.Module;",
49           "import dagger.Provides;",
50           "import dagger.producers.Production;",
51           "import java.util.concurrent.Executor;",
52           "",
53           "@Module",
54           "class ExecutorModule {",
55           "  @Provides @Production Executor executor() {",
56           "    return MoreExecutors.directExecutor();",
57           "  }",
58           "}");
59 
componentWithUnprovidedInput()60   @Test public void componentWithUnprovidedInput() {
61     Source component =
62         CompilerTests.javaSource(
63             "test.MyComponent",
64             "package test;",
65             "",
66             "import com.google.common.util.concurrent.ListenableFuture;",
67             "import dagger.producers.ProductionComponent;",
68             "",
69             "@ProductionComponent(modules = {ExecutorModule.class, FooModule.class})",
70             "interface MyComponent {",
71             "  ListenableFuture<Foo> getFoo();",
72             "}");
73     Source module =
74         CompilerTests.javaSource(
75             "test.FooModule",
76             "package test;",
77             "",
78             "import dagger.producers.ProducerModule;",
79             "import dagger.producers.Produces;",
80             "",
81             "@ProducerModule",
82             "class FooModule {",
83             "  @Produces Foo foo(Bar bar) {",
84             "    return null;",
85             "  }",
86             "}");
87     Source foo =
88         CompilerTests.javaSource(
89             "test.Foo",
90             "package test;",
91             "",
92             "class Foo {}");
93     Source bar =
94         CompilerTests.javaSource(
95             "test.Bar",
96             "package test;",
97             "",
98             "class Bar {}");
99     CompilerTests.daggerCompiler(EXECUTOR_MODULE, module, component, foo, bar)
100         .withProcessingOptions(compilerMode.processorOptions())
101         .compile(
102             subject -> {
103               subject.hasErrorCount(1);
104               subject.hasErrorContaining(
105                       "Bar cannot be provided without an @Inject constructor or an @Provides- or "
106                           + "@Produces-annotated method.")
107                   .onSource(component)
108                   .onLineContaining("interface MyComponent");
109             });
110   }
111 
componentProductionWithNoDependencyChain()112   @Test public void componentProductionWithNoDependencyChain() {
113     Source component =
114         CompilerTests.javaSource(
115             "test.TestClass",
116             "package test;",
117             "",
118             "import com.google.common.util.concurrent.ListenableFuture;",
119             "import dagger.producers.ProductionComponent;",
120             "",
121             "final class TestClass {",
122             "  interface A {}",
123             "",
124             "  @ProductionComponent(modules = ExecutorModule.class)",
125             "  interface AComponent {",
126             "    ListenableFuture<A> getA();",
127             "  }",
128             "}");
129 
130     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
131         .withProcessingOptions(compilerMode.processorOptions())
132         .compile(
133             subject -> {
134               subject.hasErrorCount(1);
135               subject.hasErrorContaining(
136                       "TestClass.A cannot be provided without an @Provides- or @Produces-annotated "
137                           + "method.")
138                   .onSource(component)
139                   .onLineContaining("interface AComponent");
140             });
141   }
142 
provisionDependsOnProduction()143   @Test public void provisionDependsOnProduction() {
144     Source component =
145         CompilerTests.javaSource(
146             "test.TestClass",
147             "package test;",
148             "",
149             "import com.google.common.util.concurrent.ListenableFuture;",
150             "import dagger.Provides;",
151             "import dagger.producers.ProducerModule;",
152             "import dagger.producers.Produces;",
153             "import dagger.producers.ProductionComponent;",
154             "",
155             "final class TestClass {",
156             "  interface A {}",
157             "  interface B {}",
158             "",
159             "  @ProducerModule(includes = BModule.class)",
160             "  final class AModule {",
161             "    @Provides A a(B b) {",
162             "      return null;",
163             "    }",
164             "  }",
165             "",
166             "  @ProducerModule",
167             "  final class BModule {",
168             "    @Produces ListenableFuture<B> b() {",
169             "      return null;",
170             "    }",
171             "  }",
172             "",
173             "  @ProductionComponent(modules = {ExecutorModule.class, AModule.class})",
174             "  interface AComponent {",
175             "    ListenableFuture<A> getA();",
176             "  }",
177             "}");
178 
179     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
180         .withProcessingOptions(compilerMode.processorOptions())
181         .compile(
182             subject -> {
183               subject.hasErrorCount(1);
184               subject.hasErrorContaining(
185                       "TestClass.A is a provision, which cannot depend on a production.")
186                   .onSource(component)
187                   .onLineContaining("interface AComponent");
188             });
189 
190     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
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(
200                       "TestClass.A is a provision, which cannot depend on a production.")
201                   .onSource(component)
202                   .onLineContaining("class AModule");
203             });
204   }
205 
provisionEntryPointDependsOnProduction()206   @Test public void provisionEntryPointDependsOnProduction() {
207     Source component =
208         CompilerTests.javaSource(
209             "test.TestClass",
210             "package test;",
211             "",
212             "import com.google.common.util.concurrent.ListenableFuture;",
213             "import dagger.producers.ProducerModule;",
214             "import dagger.producers.Produces;",
215             "import dagger.producers.ProductionComponent;",
216             "",
217             "final class TestClass {",
218             "  interface A {}",
219             "",
220             "  @ProducerModule",
221             "  static final class AModule {",
222             "    @Produces ListenableFuture<A> a() {",
223             "      return null;",
224             "    }",
225             "  }",
226             "",
227             "  @ProductionComponent(modules = {ExecutorModule.class, AModule.class})",
228             "  interface AComponent {",
229             "    A getA();",
230             "  }",
231             "}");
232 
233     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
234         .withProcessingOptions(compilerMode.processorOptions())
235         .compile(
236             subject -> {
237               subject.hasErrorCount(1);
238               subject.hasErrorContaining(
239                       "TestClass.A is a provision entry-point, which cannot depend on a "
240                           + "production.")
241                   .onSource(component)
242                   .onLineContaining("interface AComponent");
243             });
244   }
245 
246   @Test
providingMultibindingWithProductions()247   public void providingMultibindingWithProductions() {
248     Source component =
249         CompilerTests.javaSource(
250             "test.TestClass",
251             "package test;",
252             "",
253             "import com.google.common.util.concurrent.ListenableFuture;",
254             "import dagger.Module;",
255             "import dagger.Provides;",
256             "import dagger.multibindings.IntoMap;",
257             "import dagger.multibindings.StringKey;",
258             "import dagger.producers.ProducerModule;",
259             "import dagger.producers.Produces;",
260             "import dagger.producers.ProductionComponent;",
261             "import java.util.Map;",
262             "import javax.inject.Provider;",
263             "",
264             "final class TestClass {",
265             "  interface A {}",
266             "  interface B {}",
267             "",
268             "  @Module",
269             "  static final class AModule {",
270             "    @Provides static A a(Map<String, Provider<Object>> map) {",
271             "      return null;",
272             "    }",
273             "",
274             "    @Provides @IntoMap @StringKey(\"a\") static Object aEntry() {",
275             "      return \"a\";",
276             "    }",
277             "  }",
278             "",
279             "  @ProducerModule",
280             "  static final class BModule {",
281             "    @Produces static B b(A a) {",
282             "      return null;",
283             "    }",
284             "",
285             "    @Produces @IntoMap @StringKey(\"b\") static Object bEntry() {",
286             "      return \"b\";",
287             "    }",
288             "  }",
289             "",
290             "  @ProductionComponent(",
291             "      modules = {ExecutorModule.class, AModule.class, BModule.class})",
292             "  interface AComponent {",
293             "    ListenableFuture<B> b();",
294             "  }",
295             "}");
296 
297     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
298         .withProcessingOptions(compilerMode.processorOptions())
299         .compile(
300             subject -> {
301               subject.hasErrorCount(1);
302               subject.hasErrorContaining(
303                       "TestClass.A is a provision, which cannot depend on a production")
304                   .onSource(component)
305                   .onLineContaining("interface AComponent");
306             });
307   }
308 
309   @Test
monitoringDependsOnUnboundType()310   public void monitoringDependsOnUnboundType() {
311     Source component =
312         CompilerTests.javaSource(
313             "test.TestClass",
314             "package test;",
315             "",
316             "import com.google.common.util.concurrent.ListenableFuture;",
317             "import dagger.Module;",
318             "import dagger.Provides;",
319             "import dagger.multibindings.IntoSet;",
320             "import dagger.producers.ProducerModule;",
321             "import dagger.producers.Produces;",
322             "import dagger.producers.ProductionComponent;",
323             "import dagger.producers.monitoring.ProductionComponentMonitor;",
324             "",
325             "final class TestClass {",
326             "  interface A {}",
327             "",
328             "  @Module",
329             "  final class MonitoringModule {",
330             "    @Provides @IntoSet",
331             "    ProductionComponentMonitor.Factory monitorFactory(A unbound) {",
332             "      return null;",
333             "    }",
334             "  }",
335             "",
336             "  @ProducerModule",
337             "  final class StringModule {",
338             "    @Produces ListenableFuture<String> str() {",
339             "      return null;",
340             "    }",
341             "  }",
342             "",
343             "  @ProductionComponent(",
344             "    modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}",
345             "  )",
346             "  interface StringComponent {",
347             "    ListenableFuture<String> getString();",
348             "  }",
349             "}");
350 
351     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
352         .withProcessingOptions(compilerMode.processorOptions())
353         .compile(
354             subject -> {
355               subject.hasErrorCount(1);
356               subject.hasErrorContaining(
357                       "TestClass.A cannot be provided without an @Provides-annotated method.")
358                   .onSource(component)
359                   .onLineContaining("interface StringComponent");
360             });
361   }
362 
363   @Test
monitoringDependsOnProduction()364   public void monitoringDependsOnProduction() {
365     Source component =
366         CompilerTests.javaSource(
367             "test.TestClass",
368             "package test;",
369             "",
370             "import com.google.common.util.concurrent.ListenableFuture;",
371             "import dagger.Module;",
372             "import dagger.Provides;",
373             "import dagger.multibindings.IntoSet;",
374             "import dagger.producers.ProducerModule;",
375             "import dagger.producers.Produces;",
376             "import dagger.producers.ProductionComponent;",
377             "import dagger.producers.monitoring.ProductionComponentMonitor;",
378             "",
379             "final class TestClass {",
380             "  interface A {}",
381             "",
382             "  @Module",
383             "  final class MonitoringModule {",
384             "    @Provides @IntoSet ProductionComponentMonitor.Factory monitorFactory(A a) {",
385             "      return null;",
386             "    }",
387             "  }",
388             "",
389             "  @ProducerModule",
390             "  final class StringModule {",
391             "    @Produces A a() {",
392             "      return null;",
393             "    }",
394             "",
395             "    @Produces ListenableFuture<String> str() {",
396             "      return null;",
397             "    }",
398             "  }",
399             "",
400             "  @ProductionComponent(",
401             "    modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}",
402             "  )",
403             "  interface StringComponent {",
404             "    ListenableFuture<String> getString();",
405             "  }",
406             "}");
407 
408     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component)
409         .withProcessingOptions(compilerMode.processorOptions())
410         .compile(
411             subject -> {
412               subject.hasErrorCount(1);
413               subject.hasErrorContaining(
414                       "Set<ProductionComponentMonitor.Factory>"
415                           + " TestClass.MonitoringModule#monitorFactory is a provision,"
416                           + " which cannot depend on a production.")
417                   .onSource(component)
418                   .onLineContaining("interface StringComponent");
419             });
420   }
421 
422   @Test
cycleNotBrokenByMap()423   public void cycleNotBrokenByMap() {
424     Source component =
425         CompilerTests.javaSource(
426             "test.TestComponent",
427             "package test;",
428             "",
429             "import com.google.common.util.concurrent.ListenableFuture;",
430             "import dagger.producers.ProductionComponent;",
431             "",
432             "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
433             "interface TestComponent {",
434             "  ListenableFuture<String> string();",
435             "}");
436     Source module =
437         CompilerTests.javaSource(
438             "test.TestModule",
439             "package test;",
440             "",
441             "import dagger.producers.ProducerModule;",
442             "import dagger.producers.Produces;",
443             "import dagger.multibindings.IntoMap;",
444             "import dagger.multibindings.StringKey;",
445             "import java.util.Map;",
446             "",
447             "@ProducerModule",
448             "final class TestModule {",
449             "  @Produces static String string(Map<String, String> map) {",
450             "    return \"string\";",
451             "  }",
452             "",
453             "  @Produces @IntoMap @StringKey(\"key\")",
454             "  static String entry(String string) {",
455             "    return string;",
456             "  }",
457             "}");
458 
459     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component, module)
460         .withProcessingOptions(compilerMode.processorOptions())
461         .compile(
462             subject -> {
463               subject.hasErrorCount(1);
464               subject.hasErrorContaining("cycle")
465                   .onSource(component)
466                   .onLineContaining("interface TestComponent");
467             });
468   }
469 
470   @Test
cycleNotBrokenByProducerMap()471   public void cycleNotBrokenByProducerMap() {
472     Source component =
473         CompilerTests.javaSource(
474             "test.TestComponent",
475             "package test;",
476             "",
477             "import com.google.common.util.concurrent.ListenableFuture;",
478             "import dagger.producers.ProductionComponent;",
479             "",
480             "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
481             "interface TestComponent {",
482             "  ListenableFuture<String> string();",
483             "}");
484     Source module =
485         CompilerTests.javaSource(
486             "test.TestModule",
487             "package test;",
488             "",
489             "import dagger.producers.Producer;",
490             "import dagger.producers.ProducerModule;",
491             "import dagger.producers.Produces;",
492             "import dagger.multibindings.StringKey;",
493             "import dagger.multibindings.IntoMap;",
494             "import java.util.Map;",
495             "",
496             "@ProducerModule",
497             "final class TestModule {",
498             "  @Produces static String string(Map<String, Producer<String>> map) {",
499             "    return \"string\";",
500             "  }",
501             "",
502             "  @Produces @IntoMap @StringKey(\"key\")",
503             "  static String entry(String string) {",
504             "    return string;",
505             "  }",
506             "}");
507 
508     CompilerTests.daggerCompiler(EXECUTOR_MODULE, component, module)
509         .withProcessingOptions(compilerMode.processorOptions())
510         .compile(
511             subject -> {
512               subject.hasErrorCount(1);
513               subject.hasErrorContaining("cycle")
514                   .onSource(component)
515                   .onLineContaining("interface TestComponent");
516             });
517   }
518 
519   @Test
componentWithBadModule()520   public void componentWithBadModule() {
521     Source badModule =
522         CompilerTests.javaSource(
523             "test.BadModule",
524             "package test;",
525             "",
526             "import dagger.BindsOptionalOf;",
527             "import dagger.multibindings.Multibinds;",
528             "import dagger.Module;",
529             "import java.util.Set;",
530             "",
531             "@Module",
532             "abstract class BadModule {",
533             "  @Multibinds",
534             "  @BindsOptionalOf",
535             "  abstract Set<String> strings();",
536             "}");
537     Source badComponent =
538         CompilerTests.javaSource(
539             "test.BadComponent",
540             "package test;",
541             "",
542             "import dagger.Component;",
543             "import java.util.Optional;",
544             "import java.util.Set;",
545             "",
546             "@Component(modules = BadModule.class)",
547             "interface BadComponent {",
548             "  Set<String> strings();",
549             "  Optional<Set<String>> optionalStrings();",
550             "}");
551 
552     CompilerTests.daggerCompiler(badModule, badComponent)
553         .withProcessingOptions(compilerMode.processorOptions())
554         .compile(
555             subject -> {
556               subject.hasErrorCount(2);
557               subject.hasErrorContaining(
558                       "strings is annotated with more than one of (dagger.Provides, "
559                           + "dagger.producers.Produces, dagger.Binds, "
560                           + "dagger.multibindings.Multibinds, dagger.BindsOptionalOf)")
561                   .onSource(badModule)
562                   .onLine(12);
563               subject.hasErrorContaining("test.BadModule has errors")
564                   .onSource(badComponent)
565                   .onLine(7);
566             });
567   }
568 }
569