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