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 package dagger.internal.codegen;
     17 
     18 import com.google.testing.compile.JavaFileObjects;
     19 import javax.tools.JavaFileObject;
     20 import org.junit.Test;
     21 import org.junit.runner.RunWith;
     22 import org.junit.runners.JUnit4;
     23 
     24 import static com.google.common.truth.Truth.assert_;
     25 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
     26 import static java.util.Arrays.asList;
     27 
     28 @RunWith(JUnit4.class)
     29 public class GraphValidationScopingTest {
     30   @Test public void componentWithoutScopeIncludesScopedBindings_Fail() {
     31     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.MyComponent",
     32         "package test;",
     33         "",
     34         "import dagger.Component;",
     35         "import javax.inject.Singleton;",
     36         "",
     37         "@Component(modules = ScopedModule.class)",
     38         "interface MyComponent {",
     39         "  ScopedType string();",
     40         "}");
     41     JavaFileObject typeFile = JavaFileObjects.forSourceLines("test.ScopedType",
     42         "package test;",
     43         "",
     44         "import javax.inject.Inject;",
     45         "import javax.inject.Singleton;",
     46         "",
     47         "@Singleton",
     48         "class ScopedType {",
     49         "  @Inject ScopedType(String s, long l, float f) {}",
     50         "}");
     51     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ScopedModule",
     52         "package test;",
     53         "",
     54         "import dagger.Module;",
     55         "import dagger.Provides;",
     56         "import javax.inject.Singleton;",
     57         "",
     58         "@Module",
     59         "class ScopedModule {",
     60         "  @Provides @Singleton String string() { return \"a string\"; }",
     61         "  @Provides long integer() { return 0L; }",
     62         "  @Provides float floatingPoint() { return 0.0f; }",
     63         "}");
     64     String errorMessage = "test.MyComponent (unscoped) may not reference scoped bindings:\n"
     65         + "      @Provides @Singleton String test.ScopedModule.string()\n"
     66         + "      @Singleton class test.ScopedType";
     67     assert_().about(javaSources()).that(asList(componentFile, typeFile, moduleFile))
     68         .processedWith(new ComponentProcessor())
     69         .failsToCompile()
     70         .withErrorContaining(errorMessage);
     71   }
     72 
     73   @Test public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() {
     74     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.MyComponent",
     75         "package test;",
     76         "",
     77         "import dagger.Component;",
     78         "import javax.inject.Singleton;",
     79         "",
     80         "@Singleton",
     81         "@Component(modules = ScopedModule.class)",
     82         "interface MyComponent {",
     83         "  ScopedType string();",
     84         "}");
     85     JavaFileObject scopeFile = JavaFileObjects.forSourceLines("test.PerTest",
     86         "package test;",
     87         "",
     88         "import javax.inject.Scope;",
     89         "",
     90         "@Scope",
     91         "@interface PerTest {}");
     92     JavaFileObject typeFile = JavaFileObjects.forSourceLines("test.ScopedType",
     93         "package test;",
     94         "",
     95         "import javax.inject.Inject;",
     96         "",
     97         "@PerTest", // incompatible scope
     98         "class ScopedType {",
     99         "  @Inject ScopedType(String s, long l, float f) {}",
    100         "}");
    101     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ScopedModule",
    102         "package test;",
    103         "",
    104         "import dagger.Module;",
    105         "import dagger.Provides;",
    106         "import javax.inject.Singleton;",
    107         "",
    108         "@Module",
    109         "class ScopedModule {",
    110         "  @Provides @PerTest String string() { return \"a string\"; }", // incompatible scope
    111         "  @Provides long integer() { return 0L; }", // unscoped - valid
    112         "  @Provides @Singleton float floatingPoint() { return 0.0f; }", // same scope - valid
    113         "}");
    114     String errorMessage = "test.MyComponent scoped with @Singleton "
    115         + "may not reference bindings with different scopes:\n"
    116         + "      @Provides @test.PerTest String test.ScopedModule.string()\n"
    117         + "      @test.PerTest class test.ScopedType";
    118     assert_().about(javaSources()).that(asList(componentFile, scopeFile, typeFile, moduleFile))
    119         .processedWith(new ComponentProcessor())
    120         .failsToCompile()
    121         .withErrorContaining(errorMessage);
    122   }
    123 
    124   @Test public void componentWithScopeMayDependOnOnlyOneScopedComponent() {
    125     // If a scoped component will have dependencies, they must only include, at most, a single
    126     // scoped component
    127     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
    128         "package test;",
    129         "",
    130         "import javax.inject.Inject;",
    131         "",
    132         "class SimpleType {",
    133         "  @Inject SimpleType() {}",
    134         "  static class A { @Inject A() {} }",
    135         "  static class B { @Inject B() {} }",
    136         "}");
    137     JavaFileObject simpleScope = JavaFileObjects.forSourceLines("test.SimpleScope",
    138         "package test;",
    139         "",
    140         "import javax.inject.Scope;",
    141         "",
    142         "@Scope @interface SimpleScope {}");
    143     JavaFileObject singletonScopedA = JavaFileObjects.forSourceLines("test.SingletonComponentA",
    144         "package test;",
    145         "",
    146         "import dagger.Component;",
    147         "import javax.inject.Singleton;",
    148         "",
    149         "@Singleton",
    150         "@Component",
    151         "interface SingletonComponentA {",
    152         "  SimpleType.A type();",
    153         "}");
    154     JavaFileObject singletonScopedB = JavaFileObjects.forSourceLines("test.SingletonComponentB",
    155         "package test;",
    156         "",
    157         "import dagger.Component;",
    158         "import javax.inject.Singleton;",
    159         "",
    160         "@Singleton",
    161         "@Component",
    162         "interface SingletonComponentB {",
    163         "  SimpleType.B type();",
    164         "}");
    165     JavaFileObject scopeless = JavaFileObjects.forSourceLines("test.ScopelessComponent",
    166         "package test;",
    167         "",
    168         "import dagger.Component;",
    169         "",
    170         "@Component",
    171         "interface ScopelessComponent {",
    172         "  SimpleType type();",
    173         "}");
    174     JavaFileObject simpleScoped = JavaFileObjects.forSourceLines("test.SimpleScopedComponent",
    175         "package test;",
    176         "",
    177         "import dagger.Component;",
    178         "",
    179         "@SimpleScope",
    180         "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})",
    181         "interface SimpleScopedComponent {",
    182         "  SimpleType.A type();",
    183         "}");
    184     String errorMessage =
    185         "@test.SimpleScope test.SimpleScopedComponent depends on more than one scoped component:\n"
    186         + "      @Singleton test.SingletonComponentA\n"
    187         + "      @Singleton test.SingletonComponentB";
    188     assert_().about(javaSources())
    189         .that(
    190             asList(type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless))
    191         .processedWith(new ComponentProcessor())
    192         .failsToCompile()
    193         .withErrorContaining(errorMessage);
    194   }
    195 
    196   @Test public void componentWithoutScopeCannotDependOnScopedComponent() {
    197     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
    198         "package test;",
    199         "",
    200         "import javax.inject.Inject;",
    201         "",
    202         "class SimpleType {",
    203         "  @Inject SimpleType() {}",
    204         "}");
    205     JavaFileObject scopedComponent = JavaFileObjects.forSourceLines("test.ScopedComponent",
    206         "package test;",
    207         "",
    208         "import dagger.Component;",
    209         "import javax.inject.Singleton;",
    210         "",
    211         "@Singleton",
    212         "@Component",
    213         "interface ScopedComponent {",
    214         "  SimpleType type();",
    215         "}");
    216     JavaFileObject unscopedComponent = JavaFileObjects.forSourceLines("test.UnscopedComponent",
    217         "package test;",
    218         "",
    219         "import dagger.Component;",
    220         "import javax.inject.Singleton;",
    221         "",
    222         "@Component(dependencies = ScopedComponent.class)",
    223         "interface UnscopedComponent {",
    224         "  SimpleType type();",
    225         "}");
    226     String errorMessage =
    227         "test.UnscopedComponent (unscoped) cannot depend on scoped components:\n"
    228         + "      @Singleton test.ScopedComponent";
    229     assert_().about(javaSources())
    230         .that(asList(type, scopedComponent, unscopedComponent))
    231         .processedWith(new ComponentProcessor())
    232         .failsToCompile()
    233         .withErrorContaining(errorMessage);
    234   }
    235 
    236   @Test public void componentWithSingletonScopeMayNotDependOnOtherScope() {
    237     // Singleton must be the widest lifetime of present scopes.
    238     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
    239         "package test;",
    240         "",
    241         "import javax.inject.Inject;",
    242         "",
    243         "class SimpleType {",
    244         "  @Inject SimpleType() {}",
    245         "}");
    246     JavaFileObject simpleScope = JavaFileObjects.forSourceLines("test.SimpleScope",
    247         "package test;",
    248         "",
    249         "import javax.inject.Scope;",
    250         "",
    251         "@Scope @interface SimpleScope {}");
    252     JavaFileObject simpleScoped = JavaFileObjects.forSourceLines("test.SimpleScopedComponent",
    253         "package test;",
    254         "",
    255         "import dagger.Component;",
    256         "",
    257         "@SimpleScope",
    258         "@Component",
    259         "interface SimpleScopedComponent {",
    260         "  SimpleType type();",
    261         "}");
    262     JavaFileObject singletonScoped = JavaFileObjects.forSourceLines("test.SingletonComponent",
    263         "package test;",
    264         "",
    265         "import dagger.Component;",
    266         "import javax.inject.Singleton;",
    267         "",
    268         "@Singleton",
    269         "@Component(dependencies = SimpleScopedComponent.class)",
    270         "interface SingletonComponent {",
    271         "  SimpleType type();",
    272         "}");
    273     String errorMessage =
    274         "This @Singleton component cannot depend on scoped components:\n"
    275         + "      @test.SimpleScope test.SimpleScopedComponent";
    276     assert_().about(javaSources())
    277         .that(asList(type, simpleScope, simpleScoped, singletonScoped))
    278         .processedWith(new ComponentProcessor())
    279         .failsToCompile()
    280         .withErrorContaining(errorMessage);
    281   }
    282 
    283   @Test public void componentScopeAncestryMustNotCycle() {
    284     // The dependency relationship of components is necessarily from shorter lifetimes to
    285     // longer lifetimes.  The scoping annotations must reflect this, and so one cannot declare
    286     // scopes on components such that they cycle.
    287     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
    288         "package test;",
    289         "",
    290         "import javax.inject.Inject;",
    291         "",
    292         "class SimpleType {",
    293         "  @Inject SimpleType() {}",
    294         "}");
    295     JavaFileObject scopeA = JavaFileObjects.forSourceLines("test.ScopeA",
    296         "package test;",
    297         "",
    298         "import javax.inject.Scope;",
    299         "",
    300         "@Scope @interface ScopeA {}");
    301     JavaFileObject scopeB = JavaFileObjects.forSourceLines("test.ScopeB",
    302         "package test;",
    303         "",
    304         "import javax.inject.Scope;",
    305         "",
    306         "@Scope @interface ScopeB {}");
    307     JavaFileObject longLifetime = JavaFileObjects.forSourceLines("test.ComponentLong",
    308         "package test;",
    309         "",
    310         "import dagger.Component;",
    311         "",
    312         "@ScopeA",
    313         "@Component",
    314         "interface ComponentLong {",
    315         "  SimpleType type();",
    316         "}");
    317     JavaFileObject mediumLifetime = JavaFileObjects.forSourceLines("test.ComponentMedium",
    318         "package test;",
    319         "",
    320         "import dagger.Component;",
    321         "",
    322         "@ScopeB",
    323         "@Component(dependencies = ComponentLong.class)",
    324         "interface ComponentMedium {",
    325         "  SimpleType type();",
    326         "}");
    327     JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort",
    328         "package test;",
    329         "",
    330         "import dagger.Component;",
    331         "",
    332         "@ScopeA",
    333         "@Component(dependencies = ComponentMedium.class)",
    334         "interface ComponentShort {",
    335         "  SimpleType type();",
    336         "}");
    337     String errorMessage =
    338         "test.ComponentShort depends on scoped components in a non-hierarchical scope ordering:\n"
    339         + "      @test.ScopeA test.ComponentLong\n"
    340         + "      @test.ScopeB test.ComponentMedium\n"
    341         + "      @test.ScopeA test.ComponentShort";
    342     assert_().about(javaSources())
    343         .that(asList(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime))
    344         .processedWith(new ComponentProcessor())
    345         .failsToCompile()
    346         .withErrorContaining(errorMessage);
    347   }
    348 }
    349