Home | History | Annotate | Download | only in codegen
      1 /*
      2  * Copyright (C) 2014 Google, Inc.
      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 // TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest.
     17 package dagger.internal.codegen;
     18 
     19 import com.google.common.collect.ImmutableList;
     20 import com.google.testing.compile.JavaFileObjects;
     21 import javax.tools.JavaFileObject;
     22 import org.junit.Test;
     23 import org.junit.runner.RunWith;
     24 import org.junit.runners.JUnit4;
     25 
     26 import static com.google.common.truth.Truth.assertAbout;
     27 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
     28 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
     29 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_ABSTRACT;
     30 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_MUST_RETURN_A_VALUE;
     31 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_NOT_IN_MODULE;
     32 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_PRIVATE;
     33 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_SET_VALUES_RAW_SET;
     34 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_TYPE_PARAMETER;
     35 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME;
     36 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_RAW_FUTURE;
     37 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_RETURN_TYPE;
     38 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_SET_VALUES_RETURN_SET;
     39 import static dagger.internal.codegen.ErrorMessages.PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS;
     40 
     41 @RunWith(JUnit4.class)
     42 public class ProducerModuleFactoryGeneratorTest {
     43   private String formatErrorMessage(String msg) {
     44     return String.format(msg, "Produces");
     45   }
     46 
     47   private String formatModuleErrorMessage(String msg) {
     48     return String.format(msg, "Produces", "ProducerModule");
     49   }
     50 
     51   @Test public void producesMethodNotInModule() {
     52     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
     53         "package test;",
     54         "",
     55         "import dagger.producers.Produces;",
     56         "",
     57         "final class TestModule {",
     58         "  @Produces String produceString() {",
     59         "    return \"\";",
     60         "  }",
     61         "}");
     62     assertAbout(javaSource()).that(moduleFile)
     63         .processedWith(new ComponentProcessor())
     64         .failsToCompile()
     65         .withErrorContaining(formatModuleErrorMessage(BINDING_METHOD_NOT_IN_MODULE));
     66   }
     67 
     68   @Test public void producesMethodAbstract() {
     69     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
     70         "package test;",
     71         "",
     72         "import dagger.producers.ProducerModule;",
     73         "import dagger.producers.Produces;",
     74         "",
     75         "@ProducerModule",
     76         "abstract class TestModule {",
     77         "  @Produces abstract String produceString();",
     78         "}");
     79     assertAbout(javaSource()).that(moduleFile)
     80         .processedWith(new ComponentProcessor())
     81         .failsToCompile()
     82         .withErrorContaining(formatErrorMessage(BINDING_METHOD_ABSTRACT));
     83   }
     84 
     85   @Test public void producesMethodPrivate() {
     86     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
     87         "package test;",
     88         "",
     89         "import dagger.producers.ProducerModule;",
     90         "import dagger.producers.Produces;",
     91         "",
     92         "@ProducerModule",
     93         "final class TestModule {",
     94         "  @Produces private String produceString() {",
     95         "    return \"\";",
     96         "  }",
     97         "}");
     98     assertAbout(javaSource()).that(moduleFile)
     99         .processedWith(new ComponentProcessor())
    100         .failsToCompile()
    101         .withErrorContaining(formatErrorMessage(BINDING_METHOD_PRIVATE));
    102   }
    103 
    104   @Test public void producesMethodReturnVoid() {
    105     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    106         "package test;",
    107         "",
    108         "import dagger.producers.ProducerModule;",
    109         "import dagger.producers.Produces;",
    110         "",
    111         "@ProducerModule",
    112         "final class TestModule {",
    113         "  @Produces void produceNothing() {}",
    114         "}");
    115     assertAbout(javaSource()).that(moduleFile)
    116         .processedWith(new ComponentProcessor())
    117         .failsToCompile()
    118         .withErrorContaining(formatErrorMessage(BINDING_METHOD_MUST_RETURN_A_VALUE));
    119   }
    120 
    121   @Test public void producesMethodReturnRawFuture() {
    122     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    123         "package test;",
    124         "",
    125         "import com.google.common.util.concurrent.ListenableFuture;",
    126         "import dagger.producers.ProducerModule;",
    127         "import dagger.producers.Produces;",
    128         "",
    129         "@ProducerModule",
    130         "final class TestModule {",
    131         "  @Produces ListenableFuture produceRaw() {}",
    132         "}");
    133     assertAbout(javaSource()).that(moduleFile)
    134         .processedWith(new ComponentProcessor())
    135         .failsToCompile()
    136         .withErrorContaining(PRODUCES_METHOD_RAW_FUTURE);
    137   }
    138 
    139   @Test public void producesMethodReturnWildcardFuture() {
    140     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    141         "package test;",
    142         "",
    143         "import com.google.common.util.concurrent.ListenableFuture;",
    144         "import dagger.producers.ProducerModule;",
    145         "import dagger.producers.Produces;",
    146         "",
    147         "@ProducerModule",
    148         "final class TestModule {",
    149         "  @Produces ListenableFuture<?> produceRaw() {}",
    150         "}");
    151     assertAbout(javaSource()).that(moduleFile)
    152         .processedWith(new ComponentProcessor())
    153         .failsToCompile()
    154         .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE);
    155   }
    156 
    157   @Test public void producesMethodWithTypeParameter() {
    158     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    159         "package test;",
    160         "",
    161         "import dagger.producers.ProducerModule;",
    162         "import dagger.producers.Produces;",
    163         "",
    164         "@ProducerModule",
    165         "final class TestModule {",
    166         "  @Produces <T> String produceString() {",
    167         "    return \"\";",
    168         "  }",
    169         "}");
    170     assertAbout(javaSource()).that(moduleFile)
    171         .processedWith(new ComponentProcessor())
    172         .failsToCompile()
    173         .withErrorContaining(formatErrorMessage(BINDING_METHOD_TYPE_PARAMETER));
    174   }
    175 
    176   @Test public void producesMethodSetValuesWildcard() {
    177     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    178         "package test;",
    179         "",
    180         "import static dagger.producers.Produces.Type.SET_VALUES;",
    181         "",
    182         "import dagger.producers.ProducerModule;",
    183         "import dagger.producers.Produces;",
    184         "",
    185         "import java.util.Set;",
    186         "",
    187         "@ProducerModule",
    188         "final class TestModule {",
    189         "  @Produces(type = SET_VALUES) Set<?> produceWildcard() {",
    190         "    return null;",
    191         "  }",
    192         "}");
    193     assertAbout(javaSource()).that(moduleFile)
    194         .processedWith(new ComponentProcessor())
    195         .failsToCompile()
    196         .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE);
    197   }
    198 
    199   @Test public void producesMethodSetValuesRawSet() {
    200     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    201         "package test;",
    202         "",
    203         "import static dagger.producers.Produces.Type.SET_VALUES;",
    204         "",
    205         "import dagger.producers.ProducerModule;",
    206         "import dagger.producers.Produces;",
    207         "",
    208         "import java.util.Set;",
    209         "",
    210         "@ProducerModule",
    211         "final class TestModule {",
    212         "  @Produces(type = SET_VALUES) Set produceSomething() {",
    213         "    return null;",
    214         "  }",
    215         "}");
    216     assertAbout(javaSource()).that(moduleFile)
    217         .processedWith(new ComponentProcessor())
    218         .failsToCompile()
    219         .withErrorContaining(formatErrorMessage(BINDING_METHOD_SET_VALUES_RAW_SET));
    220   }
    221 
    222   @Test public void producesMethodSetValuesNotASet() {
    223     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    224         "package test;",
    225         "",
    226         "import static dagger.producers.Produces.Type.SET_VALUES;",
    227         "",
    228         "import dagger.producers.ProducerModule;",
    229         "import dagger.producers.Produces;",
    230         "",
    231         "import java.util.List;",
    232         "",
    233         "@ProducerModule",
    234         "final class TestModule {",
    235         "  @Produces(type = SET_VALUES) List<String> produceStrings() {",
    236         "    return null;",
    237         "  }",
    238         "}");
    239     assertAbout(javaSource()).that(moduleFile)
    240         .processedWith(new ComponentProcessor())
    241         .failsToCompile()
    242         .withErrorContaining(PRODUCES_METHOD_SET_VALUES_RETURN_SET);
    243   }
    244 
    245   @Test public void producesMethodSetValuesWildcardInFuture() {
    246     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    247         "package test;",
    248         "",
    249         "import static dagger.producers.Produces.Type.SET_VALUES;",
    250         "",
    251         "import com.google.common.util.concurrent.ListenableFuture;",
    252         "import dagger.producers.ProducerModule;",
    253         "import dagger.producers.Produces;",
    254         "",
    255         "import java.util.Set;",
    256         "",
    257         "@ProducerModule",
    258         "final class TestModule {",
    259         "  @Produces(type = SET_VALUES) ListenableFuture<Set<?>> produceWildcard() {",
    260         "    return null;",
    261         "  }",
    262         "}");
    263     assertAbout(javaSource()).that(moduleFile)
    264         .processedWith(new ComponentProcessor())
    265         .failsToCompile()
    266         .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE);
    267   }
    268 
    269   @Test public void producesMethodSetValuesFutureRawSet() {
    270     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    271         "package test;",
    272         "",
    273         "import static dagger.producers.Produces.Type.SET_VALUES;",
    274         "",
    275         "import com.google.common.util.concurrent.ListenableFuture;",
    276         "import dagger.producers.ProducerModule;",
    277         "import dagger.producers.Produces;",
    278         "",
    279         "import java.util.Set;",
    280         "",
    281         "@ProducerModule",
    282         "final class TestModule {",
    283         "  @Produces(type = SET_VALUES) ListenableFuture<Set> produceSomething() {",
    284         "    return null;",
    285         "  }",
    286         "}");
    287     assertAbout(javaSource()).that(moduleFile)
    288         .processedWith(new ComponentProcessor())
    289         .failsToCompile()
    290         .withErrorContaining(formatErrorMessage(BINDING_METHOD_SET_VALUES_RAW_SET));
    291   }
    292 
    293   @Test public void producesMethodSetValuesFutureNotASet() {
    294     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    295         "package test;",
    296         "",
    297         "import static dagger.producers.Produces.Type.SET_VALUES;",
    298         "",
    299         "import com.google.common.util.concurrent.ListenableFuture;",
    300         "import dagger.producers.ProducerModule;",
    301         "import dagger.producers.Produces;",
    302         "",
    303         "import java.util.List;",
    304         "",
    305         "@ProducerModule",
    306         "final class TestModule {",
    307         "  @Produces(type = SET_VALUES) ListenableFuture<List<String>> produceStrings() {",
    308         "    return null;",
    309         "  }",
    310         "}");
    311     assertAbout(javaSource()).that(moduleFile)
    312         .processedWith(new ComponentProcessor())
    313         .failsToCompile()
    314         .withErrorContaining(PRODUCES_METHOD_SET_VALUES_RETURN_SET);
    315   }
    316 
    317   @Test public void multipleProducesMethodsWithSameName() {
    318     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    319         "package test;",
    320         "",
    321         "import dagger.producers.ProducerModule;",
    322         "import dagger.producers.Produces;",
    323         "",
    324         "@ProducerModule",
    325         "final class TestModule {",
    326         "  @Produces Object produce(int i) {",
    327         "    return i;",
    328         "  }",
    329         "",
    330         "  @Produces String produce() {",
    331         "    return \"\";",
    332         "  }",
    333         "}");
    334     String errorMessage = String.format(BINDING_METHOD_WITH_SAME_NAME, "Produces");
    335     assertAbout(javaSource()).that(moduleFile)
    336         .processedWith(new ComponentProcessor())
    337         .failsToCompile()
    338         .withErrorContaining(errorMessage).in(moduleFile).onLine(8)
    339         .and().withErrorContaining(errorMessage).in(moduleFile).onLine(12);
    340   }
    341 
    342   @Test
    343   public void privateModule() {
    344     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
    345         "package test;",
    346         "",
    347         "import dagger.producers.ProducerModule;",
    348         "",
    349         "final class Enclosing {",
    350         "  @ProducerModule private static final class PrivateModule {",
    351         "  }",
    352         "}");
    353     assertAbout(javaSource())
    354         .that(moduleFile)
    355         .processedWith(new ComponentProcessor())
    356         .failsToCompile()
    357         .withErrorContaining("Modules cannot be private.")
    358         .in(moduleFile).onLine(6);
    359   }
    360 
    361   @Test
    362   public void enclosedInPrivateModule() {
    363     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
    364         "package test;",
    365         "",
    366         "import dagger.producers.ProducerModule;",
    367         "",
    368         "final class Enclosing {",
    369         "  private static final class PrivateEnclosing {",
    370         "    @ProducerModule static final class TestModule {",
    371         "    }",
    372         "  }",
    373         "}");
    374     assertAbout(javaSource())
    375         .that(moduleFile)
    376         .processedWith(new ComponentProcessor())
    377         .failsToCompile()
    378         .withErrorContaining("Modules cannot be enclosed in private types.")
    379         .in(moduleFile).onLine(7);
    380   }
    381 
    382   @Test
    383   public void includesNonModule() {
    384     JavaFileObject xFile =
    385         JavaFileObjects.forSourceLines("test.X", "package test;", "", "public final class X {}");
    386     JavaFileObject moduleFile =
    387         JavaFileObjects.forSourceLines(
    388             "test.FooModule",
    389             "package test;",
    390             "",
    391             "import dagger.producers.ProducerModule;",
    392             "",
    393             "@ProducerModule(includes = X.class)",
    394             "public final class FooModule {",
    395             "}");
    396     assertAbout(javaSources())
    397         .that(ImmutableList.of(xFile, moduleFile))
    398         .processedWith(new ComponentProcessor())
    399         .failsToCompile()
    400         .withErrorContaining(
    401             String.format(
    402                 ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED,
    403                 "X",
    404                 "one of @Module, @ProducerModule"));
    405   }
    406 
    407   @Test
    408   public void publicModuleNonPublicIncludes() {
    409     JavaFileObject publicModuleFile = JavaFileObjects.forSourceLines("test.PublicModule",
    410         "package test;",
    411         "",
    412         "import dagger.producers.ProducerModule;",
    413         "",
    414         "@ProducerModule(includes = {",
    415         "    NonPublicModule1.class, OtherPublicModule.class, NonPublicModule2.class",
    416         "})",
    417         "public final class PublicModule {",
    418         "}");
    419     JavaFileObject nonPublicModule1File = JavaFileObjects.forSourceLines("test.NonPublicModule1",
    420         "package test;",
    421         "",
    422         "import dagger.producers.ProducerModule;",
    423         "",
    424         "@ProducerModule",
    425         "final class NonPublicModule1 {",
    426         "}");
    427     JavaFileObject nonPublicModule2File = JavaFileObjects.forSourceLines("test.NonPublicModule2",
    428         "package test;",
    429         "",
    430         "import dagger.producers.ProducerModule;",
    431         "",
    432         "@ProducerModule",
    433         "final class NonPublicModule2 {",
    434         "}");
    435     JavaFileObject otherPublicModuleFile = JavaFileObjects.forSourceLines("test.OtherPublicModule",
    436         "package test;",
    437         "",
    438         "import dagger.producers.ProducerModule;",
    439         "",
    440         "@ProducerModule",
    441         "public final class OtherPublicModule {",
    442         "}");
    443     assertAbout(javaSources())
    444         .that(ImmutableList.of(
    445             publicModuleFile, nonPublicModule1File, nonPublicModule2File, otherPublicModuleFile))
    446         .processedWith(new ComponentProcessor())
    447         .failsToCompile()
    448         .withErrorContaining("This module is public, but it includes non-public "
    449             + "(or effectively non-public) modules. "
    450             + "Either reduce the visibility of this module or make "
    451             + "test.NonPublicModule1 and test.NonPublicModule2 public.")
    452         .in(publicModuleFile).onLine(8);
    453   }
    454 
    455   @Test public void singleProducesMethodNoArgsFuture() {
    456     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    457         "package test;",
    458         "",
    459         "import com.google.common.util.concurrent.ListenableFuture;",
    460         "import dagger.producers.ProducerModule;",
    461         "import dagger.producers.Produces;",
    462         "",
    463         "@ProducerModule",
    464         "final class TestModule {",
    465         "  @Produces ListenableFuture<String> produceString() {",
    466         "    return null;",
    467         "  }",
    468         "}");
    469     JavaFileObject factoryFile =
    470         JavaFileObjects.forSourceLines(
    471             "TestModule_ProduceStringFactory",
    472             "package test;",
    473             "",
    474             "import com.google.common.util.concurrent.AsyncFunction;",
    475             "import com.google.common.util.concurrent.Futures;",
    476             "import com.google.common.util.concurrent.ListenableFuture;",
    477             "import dagger.producers.internal.AbstractProducer;",
    478             "import dagger.producers.monitoring.ProducerMonitor;",
    479             "import dagger.producers.monitoring.ProducerToken;",
    480             "import dagger.producers.monitoring.ProductionComponentMonitor;",
    481             "import java.util.concurrent.Executor;",
    482             "import javax.annotation.Generated;",
    483             "import javax.inject.Provider;",
    484             "",
    485             "@Generated(\"dagger.internal.codegen.ComponentProcessor\")",
    486             "public final class TestModule_ProduceStringFactory extends AbstractProducer<String> {",
    487             "  private final TestModule module;",
    488             "  private final Executor executor;",
    489             "  private final Provider<ProductionComponentMonitor> monitorProvider;",
    490             "",
    491             "  public TestModule_ProduceStringFactory(",
    492             "      TestModule module,",
    493             "      Executor executor,",
    494             "      Provider<ProductionComponentMonitor> monitorProvider) {",
    495             "    super(",
    496             "        monitorProvider,",
    497             "        ProducerToken.create(TestModule_ProduceStringFactory.class));",
    498             "    assert module != null;",
    499             "    this.module = module;",
    500             "    assert executor != null;",
    501             "    this.executor = executor;",
    502             "    assert monitorProvider != null;",
    503             "    this.monitorProvider = monitorProvider;",
    504             "  }",
    505             "",
    506             "  @Override protected ListenableFuture<String> compute(",
    507             "      final ProducerMonitor monitor) {",
    508             "    return Futures.transform(",
    509             "      Futures.<Void>immediateFuture(null),",
    510             "      new AsyncFunction<Void, String>() {",
    511             "        @Override public ListenableFuture<String> apply(Void ignoredVoidArg) {",
    512             "          monitor.methodStarting();",
    513             "          try {",
    514             "            return module.produceString();",
    515             "          } finally {",
    516             "            monitor.methodFinished();",
    517             "          }",
    518             "        }",
    519             "      }, executor);",
    520             "  }",
    521             "}");
    522     assertAbout(javaSource())
    523         .that(moduleFile)
    524         .processedWith(new ComponentProcessor())
    525         .compilesWithoutError()
    526         .and()
    527         .generatesSources(factoryFile);
    528   }
    529 
    530   private static final JavaFileObject QUALIFIER_A =
    531       JavaFileObjects.forSourceLines("test.QualifierA",
    532           "package test;",
    533           "",
    534           "import javax.inject.Qualifier;",
    535           "",
    536           "@Qualifier @interface QualifierA {}");
    537   private static final JavaFileObject QUALIFIER_B =
    538       JavaFileObjects.forSourceLines("test.QualifierB",
    539           "package test;",
    540           "",
    541           "import javax.inject.Qualifier;",
    542           "",
    543           "@Qualifier @interface QualifierB {}");
    544 
    545   @Test public void producesMethodMultipleQualifiers() {
    546     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
    547         "package test;",
    548         "",
    549         "import dagger.producers.ProducerModule;",
    550         "import dagger.producers.Produces;",
    551         "",
    552         "@ProducerModule",
    553         "final class TestModule {",
    554         "  @Produces @QualifierA @QualifierB abstract String produceString() {",
    555         "    return \"\";",
    556         "  }",
    557         "}");
    558     assertAbout(javaSources()).that(ImmutableList.of(moduleFile, QUALIFIER_A, QUALIFIER_B))
    559         .processedWith(new ComponentProcessor())
    560         .failsToCompile()
    561         .withErrorContaining(PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS);
    562   }
    563 }
    564