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