• 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 dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
21 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod;
22 import static java.lang.annotation.RetentionPolicy.RUNTIME;
23 
24 import androidx.room.compiler.processing.util.Source;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.util.concurrent.ListenableFuture;
27 import dagger.testing.compile.CompilerTests;
28 import dagger.testing.golden.GoldenFileRule;
29 import java.lang.annotation.Retention;
30 import javax.inject.Qualifier;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 import org.junit.runners.JUnit4;
35 
36 @RunWith(JUnit4.class)
37 public class ProducerModuleFactoryGeneratorTest {
38 
39   @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
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     Source moduleFile =
160         CompilerTests.javaSource(
161             "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     CompilerTests.daggerCompiler(moduleFile)
180         .compile(
181             subject -> {
182               subject.hasErrorCount(2);
183               subject.hasErrorContaining(errorMessage).onSource(moduleFile).onLine(8);
184               subject.hasErrorContaining(errorMessage).onSource(moduleFile).onLine(12);
185             });
186   }
187 
188   @Test
producesMethodThrowsThrowable()189   public void producesMethodThrowsThrowable() {
190     assertThatProductionModuleMethod("@Produces int produceInt() throws Throwable { return 0; }")
191         .hasError(
192             "@Produces methods may only throw unchecked exceptions or exceptions subclassing "
193                 + "Exception");
194   }
195 
producesMethodWithScope()196   @Test public void producesMethodWithScope() {
197     assertThatProductionModuleMethod("@Produces @Singleton String str() { return \"\"; }")
198         .hasError("@Produces methods cannot be scoped");
199   }
200 
201   @Test
privateModule()202   public void privateModule() {
203     Source moduleFile =
204         CompilerTests.javaSource("test.Enclosing",
205         "package test;",
206         "",
207         "import dagger.producers.ProducerModule;",
208         "",
209         "final class Enclosing {",
210         "  @ProducerModule private static final class PrivateModule {",
211         "  }",
212         "}");
213     CompilerTests.daggerCompiler(moduleFile)
214         .compile(
215             subject -> {
216               subject.hasErrorCount(1);
217               subject.hasErrorContaining("Modules cannot be private")
218                   .onSource(moduleFile)
219                   .onLine(6);
220             });
221   }
222 
223 
224   @Test
enclosedInPrivateModule()225   public void enclosedInPrivateModule() {
226     Source moduleFile =
227         CompilerTests.javaSource(
228             "test.Enclosing",
229             "package test;",
230             "",
231             "import dagger.producers.ProducerModule;",
232             "",
233             "final class Enclosing {",
234             "  private static final class PrivateEnclosing {",
235             "    @ProducerModule static final class TestModule {",
236             "    }",
237             "  }",
238             "}");
239     CompilerTests.daggerCompiler(moduleFile)
240         .compile(
241             subject -> {
242               subject.hasErrorCount(1);
243               subject.hasErrorContaining("Modules cannot be enclosed in private types")
244                   .onSource(moduleFile)
245                   .onLine(7);
246             });
247   }
248 
249   @Test
includesNonModule()250   public void includesNonModule() {
251     Source xFile =
252         CompilerTests.javaSource(
253             "test.X",
254             "package test;",
255             "",
256             "public final class X {}");
257     Source moduleFile =
258         CompilerTests.javaSource(
259             "test.FooModule",
260             "package test;",
261             "",
262             "import dagger.producers.ProducerModule;",
263             "",
264             "@ProducerModule(includes = X.class)",
265             "public final class FooModule {",
266             "}");
267     CompilerTests.daggerCompiler(xFile, moduleFile)
268         .compile(
269             subject -> {
270               subject.hasErrorCount(1);
271               subject.hasErrorContaining(
272                       "X is listed as a module, but is not annotated with one of @Module, "
273                           + "@ProducerModule");
274             });
275   }
276 
277   // TODO(ronshapiro): merge this with the equivalent test in ModuleFactoryGeneratorTest and make it
278   // parameterized
279   @Test
publicModuleNonPublicIncludes()280   public void publicModuleNonPublicIncludes() {
281     Source publicModuleFile =
282         CompilerTests.javaSource(
283             "test.PublicModule",
284             "package test;",
285             "",
286             "import dagger.producers.ProducerModule;",
287             "",
288             "@ProducerModule(includes = {",
289             "    BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class",
290             "})",
291             "public final class PublicModule {}");
292     Source badNonPublicModuleFile =
293         CompilerTests.javaSource(
294             "test.BadNonPublicModule",
295             "package test;",
296             "",
297             "import dagger.producers.ProducerModule;",
298             "import dagger.producers.Produces;",
299             "",
300             "@ProducerModule",
301             "final class BadNonPublicModule {",
302             "  @Produces",
303             "  int produceInt() {",
304             "    return 42;",
305             "  }",
306             "}");
307     Source okNonPublicModuleFile =
308         CompilerTests.javaSource(
309             "test.OkNonPublicModule",
310             "package test;",
311             "",
312             "import dagger.producers.ProducerModule;",
313             "import dagger.producers.Produces;",
314             "",
315             "@ProducerModule",
316             "final class OkNonPublicModule {",
317             "  @Produces",
318             "  static String produceString() {",
319             "    return \"foo\";",
320             "  }",
321             "}");
322     Source otherPublicModuleFile =
323         CompilerTests.javaSource(
324             "test.OtherPublicModule",
325             "package test;",
326             "",
327             "import dagger.producers.ProducerModule;",
328             "",
329             "@ProducerModule",
330             "public final class OtherPublicModule {",
331             "}");
332     CompilerTests.daggerCompiler(
333             publicModuleFile,
334             badNonPublicModuleFile,
335             okNonPublicModuleFile,
336             otherPublicModuleFile)
337         .compile(
338             subject -> {
339               subject.hasErrorCount(1);
340               subject.hasErrorContaining(
341                   "This module is public, but it includes non-public (or effectively non-public) "
342                       + "modules (test.BadNonPublicModule) that have non-static, non-abstract "
343                       + "binding methods. Either reduce the visibility of this module, make the "
344                       + "included modules public, or make all of the binding methods on the "
345                       + "included modules abstract or static.")
346                   .onSource(publicModuleFile)
347                   .onLine(8);
348             });
349   }
350 
argumentNamedModuleCompiles()351   @Test public void argumentNamedModuleCompiles() {
352     Source moduleFile =
353         CompilerTests.javaSource(
354             "test.TestModule",
355             "package test;",
356             "",
357             "import dagger.producers.ProducerModule;",
358             "import dagger.producers.Produces;",
359             "",
360             "@ProducerModule",
361             "final class TestModule {",
362             "  @Produces String produceString(int module) {",
363             "    return null;",
364             "  }",
365             "}");
366     CompilerTests.daggerCompiler(moduleFile)
367         .compile(subject -> subject.hasErrorCount(0));
368   }
369 
singleProducesMethodNoArgsFuture()370   @Test public void singleProducesMethodNoArgsFuture() {
371     Source moduleFile =
372         CompilerTests.javaSource(
373             "test.TestModule",
374             "package test;",
375             "",
376             "import com.google.common.util.concurrent.ListenableFuture;",
377             "import dagger.producers.ProducerModule;",
378             "import dagger.producers.Produces;",
379             "",
380             "@ProducerModule",
381             "final class TestModule {",
382             "  @Produces ListenableFuture<String> produceString() {",
383             "    return null;",
384             "  }",
385             "}");
386     CompilerTests.daggerCompiler(moduleFile)
387         .compile(
388             subject -> {
389               subject.hasErrorCount(0);
390               subject.generatedSource(
391                   goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory"));
392             });
393   }
394 
395   @Test
singleProducesMethodNoArgsFutureWithProducerName()396   public void singleProducesMethodNoArgsFutureWithProducerName() {
397     Source moduleFile =
398         CompilerTests.javaSource(
399             "test.TestModule",
400             "package test;",
401             "",
402             "import com.google.common.util.concurrent.Futures;",
403             "import com.google.common.util.concurrent.ListenableFuture;",
404             "import dagger.producers.ProducerModule;",
405             "import dagger.producers.Produces;",
406             "",
407             "@ProducerModule",
408             "final class TestModule {",
409             "  @Produces ListenableFuture<String> produceString() {",
410             "    return Futures.immediateFuture(\"\");",
411             "  }",
412             "}");
413     CompilerTests.daggerCompiler(moduleFile)
414         .withProcessingOptions(ImmutableMap.of("dagger.writeProducerNameInToken", "ENABLED"))
415         .compile(
416             subject -> {
417               subject.hasErrorCount(0);
418               subject.generatedSource(
419                   goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory"));
420             });
421   }
422 
423   @Test
producesMethodMultipleQualifiersOnMethod()424   public void producesMethodMultipleQualifiersOnMethod() {
425     assertThatProductionModuleMethod(
426             "@Produces @QualifierA @QualifierB static String produceString() { return null; }")
427         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
428         .hasError("may not use more than one @Qualifier");
429   }
430 
431   @Test
producesMethodMultipleQualifiersOnParameter()432   public void producesMethodMultipleQualifiersOnParameter() {
433     assertThatProductionModuleMethod(
434             "@Produces static String produceString(@QualifierA @QualifierB Object input) "
435                 + "{ return null; }")
436         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
437         .hasError("may not use more than one @Qualifier");
438   }
439 
440   @Test
producesMethodWildcardDependency()441   public void producesMethodWildcardDependency() {
442     assertThatProductionModuleMethod(
443             "@Produces static String produceString(Provider<? extends Number> numberProvider) "
444                 + "{ return null; }")
445         .importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
446         .hasError(
447             "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> "
448                 + "when T is a wildcard type such as ? extends java.lang.Number");
449   }
450 
451   @Qualifier
452   @Retention(RUNTIME)
453   public @interface QualifierA {}
454 
455   @Qualifier
456   @Retention(RUNTIME)
457   public @interface QualifierB {}
458 }
459