• 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 // TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest.
18 package dagger.internal.codegen;
19 
20 import static com.google.common.truth.Truth.assertAbout;
21 import static com.google.testing.compile.CompilationSubject.assertThat;
22 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
23 import static dagger.internal.codegen.Compilers.daggerCompiler;
24 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
25 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod;
26 import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION;
27 import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
28 import static java.lang.annotation.RetentionPolicy.RUNTIME;
29 
30 import com.google.common.util.concurrent.ListenableFuture;
31 import com.google.testing.compile.Compilation;
32 import com.google.testing.compile.JavaFileObjects;
33 import java.lang.annotation.Retention;
34 import javax.inject.Qualifier;
35 import javax.tools.JavaFileObject;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 @RunWith(JUnit4.class)
41 public class ProducerModuleFactoryGeneratorTest {
42 
producesMethodNotInModule()43   @Test public void producesMethodNotInModule() {
44     assertThatMethodInUnannotatedClass("@Produces String produceString() { return null; }")
45         .hasError("@Produces methods can only be present within a @ProducerModule");
46   }
47 
producesMethodAbstract()48   @Test public void producesMethodAbstract() {
49     assertThatProductionModuleMethod("@Produces abstract String produceString();")
50         .hasError("@Produces methods cannot be abstract");
51   }
52 
producesMethodPrivate()53   @Test public void producesMethodPrivate() {
54     assertThatProductionModuleMethod("@Produces private String produceString() { return null; }")
55         .hasError("@Produces methods cannot be private");
56   }
57 
producesMethodReturnVoid()58   @Test public void producesMethodReturnVoid() {
59     assertThatProductionModuleMethod("@Produces void produceNothing() {}")
60         .hasError("@Produces methods must return a value (not void)");
61   }
62 
63   @Test
producesProvider()64   public void producesProvider() {
65     assertThatProductionModuleMethod("@Produces Provider<String> produceProvider() {}")
66         .hasError("@Produces methods must not return framework types");
67   }
68 
69   @Test
producesLazy()70   public void producesLazy() {
71     assertThatProductionModuleMethod("@Produces Lazy<String> produceLazy() {}")
72         .hasError("@Produces methods must not return framework types");
73   }
74 
75   @Test
producesMembersInjector()76   public void producesMembersInjector() {
77     assertThatProductionModuleMethod(
78             "@Produces MembersInjector<String> produceMembersInjector() {}")
79         .hasError("@Produces methods must not return framework types");
80   }
81 
82   @Test
producesProducer()83   public void producesProducer() {
84     assertThatProductionModuleMethod("@Produces Producer<String> produceProducer() {}")
85         .hasError("@Produces methods must not return framework types");
86   }
87 
88   @Test
producesProduced()89   public void producesProduced() {
90     assertThatProductionModuleMethod("@Produces Produced<String> produceProduced() {}")
91         .hasError("@Produces methods must not return framework types");
92   }
93 
producesMethodReturnRawFuture()94   @Test public void producesMethodReturnRawFuture() {
95     assertThatProductionModuleMethod("@Produces ListenableFuture produceRaw() {}")
96         .importing(ListenableFuture.class)
97         .hasError("@Produces methods cannot return a raw ListenableFuture");
98   }
99 
producesMethodReturnWildcardFuture()100   @Test public void producesMethodReturnWildcardFuture() {
101     assertThatProductionModuleMethod("@Produces ListenableFuture<?> produceRaw() {}")
102         .importing(ListenableFuture.class)
103         .hasError(
104             "@Produces methods can return only a primitive, an array, a type variable, "
105                 + "a declared type, or a ListenableFuture of one of those types");
106   }
107 
producesMethodWithTypeParameter()108   @Test public void producesMethodWithTypeParameter() {
109     assertThatProductionModuleMethod("@Produces <T> String produceString() { return null; }")
110         .hasError("@Produces methods may not have type parameters");
111   }
112 
producesMethodSetValuesWildcard()113   @Test public void producesMethodSetValuesWildcard() {
114     assertThatProductionModuleMethod(
115             "@Produces @ElementsIntoSet Set<?> produceWildcard() { return null; }")
116         .hasError(
117             "@Produces methods can return only a primitive, an array, a type variable, "
118                 + "a declared type, or a ListenableFuture of one of those types");
119   }
120 
producesMethodSetValuesRawSet()121   @Test public void producesMethodSetValuesRawSet() {
122     assertThatProductionModuleMethod(
123             "@Produces @ElementsIntoSet Set produceSomething() { return null; }")
124         .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set");
125   }
126 
producesMethodSetValuesNotASet()127   @Test public void producesMethodSetValuesNotASet() {
128     assertThatProductionModuleMethod(
129             "@Produces @ElementsIntoSet List<String> produceStrings() { return null; }")
130         .hasError(
131             "@Produces methods of type set values must return a Set or ListenableFuture of Set");
132   }
133 
producesMethodSetValuesWildcardInFuture()134   @Test public void producesMethodSetValuesWildcardInFuture() {
135     assertThatProductionModuleMethod(
136             "@Produces @ElementsIntoSet "
137                 + "ListenableFuture<Set<?>> produceWildcard() { return null; }")
138         .importing(ListenableFuture.class)
139         .hasError(
140             "@Produces methods can return only a primitive, an array, a type variable, "
141                 + "a declared type, or a ListenableFuture of one of those types");
142   }
143 
producesMethodSetValuesFutureRawSet()144   @Test public void producesMethodSetValuesFutureRawSet() {
145     assertThatProductionModuleMethod(
146             "@Produces @ElementsIntoSet ListenableFuture<Set> produceSomething() { return null; }")
147         .importing(ListenableFuture.class)
148         .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set");
149   }
150 
producesMethodSetValuesFutureNotASet()151   @Test public void producesMethodSetValuesFutureNotASet() {
152     assertThatProductionModuleMethod(
153             "@Produces @ElementsIntoSet "
154                 + "ListenableFuture<List<String>> produceStrings() { return null; }")
155         .importing(ListenableFuture.class)
156         .hasError(
157             "@Produces methods of type set values must return a Set or ListenableFuture of Set");
158   }
159 
multipleProducesMethodsWithSameName()160   @Test public void multipleProducesMethodsWithSameName() {
161     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
162         "package test;",
163         "",
164         "import dagger.producers.ProducerModule;",
165         "import dagger.producers.Produces;",
166         "",
167         "@ProducerModule",
168         "final class TestModule {",
169         "  @Produces Object produce(int i) {",
170         "    return i;",
171         "  }",
172         "",
173         "  @Produces String produce() {",
174         "    return \"\";",
175         "  }",
176         "}");
177     String errorMessage =
178         "Cannot have more than one binding method with the same name in a single module";
179     Compilation compilation = daggerCompiler().compile(moduleFile);
180     assertThat(compilation).failed();
181     assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(8);
182     assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(12);
183   }
184 
185   @Test
producesMethodThrowsThrowable()186   public void producesMethodThrowsThrowable() {
187     assertThatProductionModuleMethod("@Produces int produceInt() throws Throwable { return 0; }")
188         .hasError(
189             "@Produces methods may only throw unchecked exceptions or exceptions subclassing "
190                 + "Exception");
191   }
192 
producesMethodWithScope()193   @Test public void producesMethodWithScope() {
194     assertThatProductionModuleMethod("@Produces @Singleton String str() { return \"\"; }")
195         .hasError("@Produces methods cannot be scoped");
196   }
197 
198   @Test
privateModule()199   public void privateModule() {
200     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
201         "package test;",
202         "",
203         "import dagger.producers.ProducerModule;",
204         "",
205         "final class Enclosing {",
206         "  @ProducerModule private static final class PrivateModule {",
207         "  }",
208         "}");
209     Compilation compilation = daggerCompiler().compile(moduleFile);
210     assertThat(compilation).failed();
211     assertThat(compilation)
212         .hadErrorContaining("Modules cannot be private")
213         .inFile(moduleFile)
214         .onLine(6);
215   }
216 
217   @Test
enclosedInPrivateModule()218   public void enclosedInPrivateModule() {
219     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
220         "package test;",
221         "",
222         "import dagger.producers.ProducerModule;",
223         "",
224         "final class Enclosing {",
225         "  private static final class PrivateEnclosing {",
226         "    @ProducerModule static final class TestModule {",
227         "    }",
228         "  }",
229         "}");
230     Compilation compilation = daggerCompiler().compile(moduleFile);
231     assertThat(compilation).failed();
232     assertThat(compilation)
233         .hadErrorContaining("Modules cannot be enclosed in private types")
234         .inFile(moduleFile)
235         .onLine(7);
236   }
237 
238   @Test
includesNonModule()239   public void includesNonModule() {
240     JavaFileObject xFile =
241         JavaFileObjects.forSourceLines("test.X", "package test;", "", "public final class X {}");
242     JavaFileObject moduleFile =
243         JavaFileObjects.forSourceLines(
244             "test.FooModule",
245             "package test;",
246             "",
247             "import dagger.producers.ProducerModule;",
248             "",
249             "@ProducerModule(includes = X.class)",
250             "public final class FooModule {",
251             "}");
252     Compilation compilation = daggerCompiler().compile(xFile, moduleFile);
253     assertThat(compilation).failed();
254     assertThat(compilation)
255         .hadErrorContaining(
256             "X is listed as a module, but is not annotated with one of @Module, @ProducerModule");
257   }
258 
259   // TODO(ronshapiro): merge this with the equivalent test in ModuleFactoryGeneratorTest and make it
260   // parameterized
261   @Test
publicModuleNonPublicIncludes()262   public void publicModuleNonPublicIncludes() {
263     JavaFileObject publicModuleFile = JavaFileObjects.forSourceLines("test.PublicModule",
264         "package test;",
265         "",
266         "import dagger.producers.ProducerModule;",
267         "",
268         "@ProducerModule(includes = {",
269         "    BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class",
270         "})",
271         "public final class PublicModule {",
272         "}");
273     JavaFileObject badNonPublicModuleFile =
274         JavaFileObjects.forSourceLines(
275             "test.BadNonPublicModule",
276             "package test;",
277             "",
278             "import dagger.producers.ProducerModule;",
279             "import dagger.producers.Produces;",
280             "",
281             "@ProducerModule",
282             "final class BadNonPublicModule {",
283             "  @Produces",
284             "  int produceInt() {",
285             "    return 42;",
286             "  }",
287             "}");
288     JavaFileObject okNonPublicModuleFile = JavaFileObjects.forSourceLines("test.OkNonPublicModule",
289         "package test;",
290         "",
291         "import dagger.producers.ProducerModule;",
292         "import dagger.producers.Produces;",
293         "",
294         "@ProducerModule",
295         "final class OkNonPublicModule {",
296         "  @Produces",
297         "  static String produceString() {",
298         "    return \"foo\";",
299         "  }",
300         "}");
301     JavaFileObject otherPublicModuleFile = JavaFileObjects.forSourceLines("test.OtherPublicModule",
302         "package test;",
303         "",
304         "import dagger.producers.ProducerModule;",
305         "",
306         "@ProducerModule",
307         "public final class OtherPublicModule {",
308         "}");
309     Compilation compilation =
310         daggerCompiler()
311             .compile(
312                 publicModuleFile,
313                 badNonPublicModuleFile,
314                 okNonPublicModuleFile,
315                 otherPublicModuleFile);
316     assertThat(compilation).failed();
317     assertThat(compilation)
318         .hadErrorContaining(
319             "This module is public, but it includes non-public (or effectively non-public) modules "
320                 + "(test.BadNonPublicModule) that have non-static, non-abstract binding methods. "
321                 + "Either reduce the visibility of this module, make the included modules public, "
322                 + "or make all of the binding methods on the included modules abstract or static.")
323         .inFile(publicModuleFile)
324         .onLine(8);
325   }
326 
argumentNamedModuleCompiles()327   @Test public void argumentNamedModuleCompiles() {
328     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
329         "package test;",
330         "",
331         "import dagger.producers.ProducerModule;",
332         "import dagger.producers.Produces;",
333         "",
334         "@ProducerModule",
335         "final class TestModule {",
336         "  @Produces String produceString(int module) {",
337         "    return null;",
338         "  }",
339         "}");
340     Compilation compilation = daggerCompiler().compile(moduleFile);
341     assertThat(compilation).succeeded();
342   }
343 
singleProducesMethodNoArgsFuture()344   @Test public void singleProducesMethodNoArgsFuture() {
345     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
346         "package test;",
347         "",
348         "import com.google.common.util.concurrent.ListenableFuture;",
349         "import dagger.producers.ProducerModule;",
350         "import dagger.producers.Produces;",
351         "",
352         "@ProducerModule",
353         "final class TestModule {",
354         "  @Produces ListenableFuture<String> produceString() {",
355         "    return null;",
356         "  }",
357         "}");
358     JavaFileObject factoryFile =
359         JavaFileObjects.forSourceLines(
360             "TestModule_ProduceStringFactory",
361             "package test;",
362             "",
363             "import com.google.common.util.concurrent.Futures;",
364             "import com.google.common.util.concurrent.ListenableFuture;",
365             "import dagger.producers.internal.AbstractProducesMethodProducer;",
366             "import dagger.producers.monitoring.ProducerToken;",
367             "import dagger.producers.monitoring.ProductionComponentMonitor;",
368             "import java.util.concurrent.Executor;",
369             IMPORT_GENERATED_ANNOTATION,
370             "import javax.inject.Provider;",
371             "",
372             "@SuppressWarnings(\"FutureReturnValueIgnored\")",
373             GENERATED_ANNOTATION,
374             "public final class TestModule_ProduceStringFactory",
375             "    extends AbstractProducesMethodProducer<Void, String> {",
376             "  private final TestModule module;",
377             "",
378             "  private TestModule_ProduceStringFactory(",
379             "      TestModule module,",
380             "      Provider<Executor> executorProvider,",
381             "      Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
382             "    super(",
383             "        productionComponentMonitorProvider,",
384             "        ProducerToken.create(TestModule_ProduceStringFactory.class),",
385             "        executorProvider);",
386             "    this.module = module;",
387             "  }",
388             "",
389             "  public static TestModule_ProduceStringFactory create(",
390             "      TestModule module,",
391             "      Provider<Executor> executorProvider,",
392             "      Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
393             "    return new TestModule_ProduceStringFactory(",
394             "        module, executorProvider, productionComponentMonitorProvider);",
395             "  }",
396             "",
397             "  @Override protected ListenableFuture<Void> collectDependencies() {",
398             "    return Futures.<Void>immediateFuture(null);",
399             "  }",
400             "",
401             "  @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {",
402             "    return module.produceString();",
403             "  }",
404             "}");
405     assertAbout(javaSource())
406         .that(moduleFile)
407         .processedWith(new ComponentProcessor())
408         .compilesWithoutError()
409         .and()
410         .generatesSources(factoryFile);
411   }
412 
413   @Test
singleProducesMethodNoArgsFutureWithProducerName()414   public void singleProducesMethodNoArgsFutureWithProducerName() {
415     JavaFileObject moduleFile =
416         JavaFileObjects.forSourceLines(
417             "test.TestModule",
418             "package test;",
419             "",
420             "import com.google.common.util.concurrent.Futures;",
421             "import com.google.common.util.concurrent.ListenableFuture;",
422             "import dagger.producers.ProducerModule;",
423             "import dagger.producers.Produces;",
424             "",
425             "@ProducerModule",
426             "final class TestModule {",
427             "  @Produces ListenableFuture<String> produceString() {",
428             "    return Futures.immediateFuture(\"\");",
429             "  }",
430             "}");
431     JavaFileObject factoryFile =
432         JavaFileObjects.forSourceLines(
433             "TestModule_ProduceStringFactory",
434             "package test;",
435             "",
436             "import com.google.common.util.concurrent.Futures;",
437             "import com.google.common.util.concurrent.ListenableFuture;",
438             "import dagger.producers.internal.AbstractProducesMethodProducer;",
439             "import dagger.producers.monitoring.ProducerToken;",
440             "import dagger.producers.monitoring.ProductionComponentMonitor;",
441             "import java.util.concurrent.Executor;",
442             IMPORT_GENERATED_ANNOTATION,
443             "import javax.inject.Provider;",
444             "",
445             "@SuppressWarnings(\"FutureReturnValueIgnored\")",
446             GENERATED_ANNOTATION,
447             "public final class TestModule_ProduceStringFactory",
448             "    extends AbstractProducesMethodProducer<Void, String> {",
449             "  private final TestModule module;",
450             "",
451             "  private TestModule_ProduceStringFactory(",
452             "      TestModule module,",
453             "      Provider<Executor> executorProvider,",
454             "      Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
455             "    super(",
456             "        productionComponentMonitorProvider,",
457             "        ProducerToken.create(\"test.TestModule#produceString\"),",
458             "        executorProvider);",
459             "    this.module = module;",
460             "  }",
461             "",
462             "  public static TestModule_ProduceStringFactory create(",
463             "      TestModule module,",
464             "      Provider<Executor> executorProvider,",
465             "      Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
466             "    return new TestModule_ProduceStringFactory(",
467             "        module, executorProvider, productionComponentMonitorProvider);",
468             "  }",
469             "",
470             "  @Override protected ListenableFuture<Void> collectDependencies() {",
471             "    return Futures.<Void>immediateFuture(null);",
472             "  }",
473             "",
474             "  @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {",
475             "    return module.produceString();",
476             "  }",
477             "}");
478     assertAbout(javaSource())
479         .that(moduleFile)
480         .withCompilerOptions("-Adagger.writeProducerNameInToken=ENABLED")
481         .processedWith(new ComponentProcessor())
482         .compilesWithoutError()
483         .and()
484         .generatesSources(factoryFile);
485   }
486 
487   @Test
producesMethodMultipleQualifiersOnMethod()488   public void producesMethodMultipleQualifiersOnMethod() {
489     assertThatProductionModuleMethod(
490             "@Produces @QualifierA @QualifierB static String produceString() { return null; }")
491         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
492         .hasError("may not use more than one @Qualifier");
493   }
494 
495   @Test
producesMethodMultipleQualifiersOnParameter()496   public void producesMethodMultipleQualifiersOnParameter() {
497     assertThatProductionModuleMethod(
498             "@Produces static String produceString(@QualifierA @QualifierB Object input) "
499                 + "{ return null; }")
500         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
501         .hasError("may not use more than one @Qualifier");
502   }
503 
504   @Test
producesMethodWildcardDependency()505   public void producesMethodWildcardDependency() {
506     assertThatProductionModuleMethod(
507             "@Produces static String produceString(Provider<? extends Number> numberProvider) "
508                 + "{ return null; }")
509         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
510         .hasError(
511             "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> "
512                 + "when T is a wildcard type such as ? extends java.lang.Number");
513   }
514 
515   @Qualifier
516   @Retention(RUNTIME)
517   public @interface QualifierA {}
518 
519   @Qualifier
520   @Retention(RUNTIME)
521   public @interface QualifierB {}
522 }
523