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.common.base.Joiner;
     19 import com.google.common.collect.ImmutableList;
     20 import com.google.testing.compile.JavaFileObjects;
     21 import java.util.Arrays;
     22 import javax.tools.JavaFileObject;
     23 import org.junit.Ignore;
     24 import org.junit.Test;
     25 import org.junit.runner.RunWith;
     26 import org.junit.runners.JUnit4;
     27 
     28 import static com.google.common.truth.Truth.assertAbout;
     29 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
     30 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
     31 import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable;
     32 
     33 @RunWith(JUnit4.class)
     34 public class GraphValidationTest {
     35   private final JavaFileObject NULLABLE = JavaFileObjects.forSourceLines("test.Nullable",
     36       "package test;",
     37       "public @interface Nullable {}");
     38 
     39   @Test public void componentOnConcreteClass() {
     40     JavaFileObject component = JavaFileObjects.forSourceLines("test.MyComponent",
     41         "package test;",
     42         "",
     43         "import dagger.Component;",
     44         "",
     45         "@Component",
     46         "interface MyComponent {",
     47         "  Foo getFoo();",
     48         "}");
     49     JavaFileObject injectable = JavaFileObjects.forSourceLines("test.Foo",
     50         "package test;",
     51         "",
     52         "import javax.inject.Inject;",
     53         "",
     54         "class Foo {",
     55         "  @Inject Foo(Bar bar) {}",
     56         "}");
     57     JavaFileObject nonInjectable = JavaFileObjects.forSourceLines("test.Bar",
     58         "package test;",
     59         "",
     60         "import javax.inject.Inject;",
     61         "",
     62         "interface Bar {}");
     63     assertAbout(javaSources()).that(Arrays.asList(component, injectable, nonInjectable))
     64         .processedWith(new ComponentProcessor())
     65         .failsToCompile()
     66         .withErrorContaining("test.Bar cannot be provided without an @Provides-annotated method.")
     67             .in(component).onLine(7);
     68   }
     69 
     70   @Test public void componentProvisionWithNoDependencyChain() {
     71     JavaFileObject component =
     72         JavaFileObjects.forSourceLines(
     73             "test.TestClass",
     74             "package test;",
     75             "",
     76             "import dagger.Component;",
     77             "import javax.inject.Qualifier;",
     78             "",
     79             "final class TestClass {",
     80             "  @Qualifier @interface Q {}",
     81             "  interface A {}",
     82             "",
     83             "  @Component()",
     84             "  interface AComponent {",
     85             "    A getA();",
     86             "    @Q A qualifiedA();",
     87             "  }",
     88             "}");
     89     assertAbout(javaSource())
     90         .that(component)
     91         .processedWith(new ComponentProcessor())
     92         .failsToCompile()
     93         .withErrorContaining(
     94             "test.TestClass.A cannot be provided without an @Provides-annotated method.")
     95         .in(component)
     96         .onLine(12)
     97         .and()
     98         .withErrorContaining(
     99             "@test.TestClass.Q test.TestClass.A "
    100                 + "cannot be provided without an @Provides-annotated method.")
    101         .in(component)
    102         .onLine(13);
    103   }
    104 
    105   @Test public void constructorInjectionWithoutAnnotation() {
    106     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass",
    107         "package test;",
    108         "",
    109         "import dagger.Component;",
    110         "import dagger.Module;",
    111         "import dagger.Provides;",
    112         "import javax.inject.Inject;",
    113         "",
    114         "final class TestClass {",
    115         "  static class A {",
    116         "    A() {}",
    117         "  }",
    118         "",
    119         "  @Component()",
    120         "  interface AComponent {",
    121         "    A getA();",
    122         "  }",
    123         "}");
    124     String expectedError = "test.TestClass.A cannot be provided without an "
    125         + "@Inject constructor or from an @Provides-annotated method.";
    126     assertAbout(javaSource()).that(component)
    127         .processedWith(new ComponentProcessor())
    128         .failsToCompile()
    129         .withErrorContaining(expectedError).in(component).onLine(15);
    130   }
    131 
    132   @Test public void membersInjectWithoutProvision() {
    133     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass",
    134         "package test;",
    135         "",
    136         "import dagger.Component;",
    137         "import dagger.Module;",
    138         "import dagger.Provides;",
    139         "import javax.inject.Inject;",
    140         "",
    141         "final class TestClass {",
    142         "  static class A {",
    143         "    @Inject A() {}",
    144         "  }",
    145         "",
    146         "  static class B {",
    147         "    @Inject A a;",
    148         "  }",
    149         "",
    150         "  @Component()",
    151         "  interface AComponent {",
    152         "    B getB();",
    153         "  }",
    154         "}");
    155     String expectedError = "test.TestClass.B cannot be provided without an "
    156         + "@Inject constructor or from an @Provides-annotated method. "
    157         + "This type supports members injection but cannot be implicitly provided.";
    158     assertAbout(javaSource()).that(component)
    159         .processedWith(new ComponentProcessor())
    160         .failsToCompile()
    161         .withErrorContaining(expectedError).in(component).onLine(19);
    162   }
    163 
    164   @Test public void cyclicDependency() {
    165     JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
    166         "package test;",
    167         "",
    168         "import dagger.Component;",
    169         "import dagger.Module;",
    170         "import dagger.Provides;",
    171         "import javax.inject.Inject;",
    172         "",
    173         "final class Outer {",
    174         "  static class A {",
    175         "    @Inject A(C cParam) {}",
    176         "  }",
    177         "",
    178         "  static class B {",
    179         "    @Inject B(A aParam) {}",
    180         "  }",
    181         "",
    182         "  static class C {",
    183         "    @Inject C(B bParam) {}",
    184         "  }",
    185         "",
    186         "  @Component()",
    187         "  interface CComponent {",
    188         "    C getC();",
    189         "  }",
    190         "}");
    191 
    192     String expectedError = "test.Outer.CComponent.getC() contains a dependency cycle:\n"
    193         + "      test.Outer.C.<init>(test.Outer.B bParam)\n"
    194         + "          [parameter: test.Outer.B bParam]\n"
    195         + "      test.Outer.B.<init>(test.Outer.A aParam)\n"
    196         + "          [parameter: test.Outer.A aParam]\n"
    197         + "      test.Outer.A.<init>(test.Outer.C cParam)\n"
    198         + "          [parameter: test.Outer.C cParam]";
    199 
    200     assertAbout(javaSource()).that(component)
    201         .processedWith(new ComponentProcessor())
    202         .failsToCompile()
    203         .withErrorContaining(expectedError).in(component).onLine(23);
    204   }
    205 
    206   @Test public void cyclicDependencyNotIncludingEntryPoint() {
    207     JavaFileObject component =
    208         JavaFileObjects.forSourceLines(
    209             "test.Outer",
    210             "package test;",
    211             "",
    212             "import dagger.Component;",
    213             "import dagger.Module;",
    214             "import dagger.Provides;",
    215             "import javax.inject.Inject;",
    216             "",
    217             "final class Outer {",
    218             "  static class A {",
    219             "    @Inject A(C cParam) {}",
    220             "  }",
    221             "",
    222             "  static class B {",
    223             "    @Inject B(A aParam) {}",
    224             "  }",
    225             "",
    226             "  static class C {",
    227             "    @Inject C(B bParam) {}",
    228             "  }",
    229             "",
    230             "  static class D {",
    231             "    @Inject D(C cParam) {}",
    232             "  }",
    233             "",
    234             "  @Component()",
    235             "  interface DComponent {",
    236             "    D getD();",
    237             "  }",
    238             "}");
    239 
    240     String expectedError = "test.Outer.DComponent.getD() contains a dependency cycle:\n"
    241         + "      test.Outer.D.<init>(test.Outer.C cParam)\n"
    242         + "          [parameter: test.Outer.C cParam]\n"
    243         + "      test.Outer.C.<init>(test.Outer.B bParam)\n"
    244         + "          [parameter: test.Outer.B bParam]\n"
    245         + "      test.Outer.B.<init>(test.Outer.A aParam)\n"
    246         + "          [parameter: test.Outer.A aParam]\n"
    247         + "      test.Outer.A.<init>(test.Outer.C cParam)\n"
    248         + "          [parameter: test.Outer.C cParam]";
    249 
    250     assertAbout(javaSource())
    251         .that(component)
    252         .processedWith(new ComponentProcessor())
    253         .failsToCompile()
    254         .withErrorContaining(expectedError)
    255         .in(component)
    256         .onLine(27);
    257   }
    258 
    259   @Test
    260   public void cyclicDependencyNotBrokenByMapBinding() {
    261     JavaFileObject component =
    262         JavaFileObjects.forSourceLines(
    263             "test.Outer",
    264             "package test;",
    265             "",
    266             "import dagger.Component;",
    267             "import dagger.MapKey;",
    268             "import dagger.Module;",
    269             "import dagger.Provides;",
    270             "import java.util.Map;",
    271             "import javax.inject.Inject;",
    272             "",
    273             "final class Outer {",
    274             "  static class A {",
    275             "    @Inject A(Map<String, C> cMap) {}",
    276             "  }",
    277             "",
    278             "  static class B {",
    279             "    @Inject B(A aParam) {}",
    280             "  }",
    281             "",
    282             "  static class C {",
    283             "    @Inject C(B bParam) {}",
    284             "  }",
    285             "",
    286             "  @Component(modules = CModule.class)",
    287             "  interface CComponent {",
    288             "    C getC();",
    289             "  }",
    290             "",
    291             "  @Module",
    292             "  static class CModule {",
    293             "    @Provides(type = Provides.Type.MAP)",
    294             "    @StringKey(\"C\")",
    295             "    static C c(C c) {",
    296             "      return c;",
    297             "    }",
    298             "  }",
    299             "",
    300             "  @MapKey",
    301             "  @interface StringKey {",
    302             "    String value();",
    303             "  }",
    304             "}");
    305 
    306     String expectedError =
    307         Joiner.on('\n')
    308             .join(
    309                 "test.Outer.CComponent.getC() contains a dependency cycle:",
    310                 "      test.Outer.C.<init>(test.Outer.B bParam)",
    311                 "          [parameter: test.Outer.B bParam]",
    312                 "      test.Outer.B.<init>(test.Outer.A aParam)",
    313                 "          [parameter: test.Outer.A aParam]",
    314                 "      test.Outer.A.<init>(java.util.Map<java.lang.String,test.Outer.C> cMap)",
    315                 "          [parameter: java.util.Map<java.lang.String,test.Outer.C> cMap]",
    316                 "      test.Outer.A.<init>(java.util.Map<java.lang.String,test.Outer.C> cMap)",
    317                 "          [parameter: java.util.Map<java.lang.String,test.Outer.C> cMap]",
    318                 "      test.Outer.CModule.c(test.Outer.C c)",
    319                 "          [parameter: test.Outer.C c]");
    320 
    321     assertAbout(javaSource())
    322         .that(component)
    323         .processedWith(new ComponentProcessor())
    324         .failsToCompile()
    325         .withErrorContaining(expectedError)
    326         .in(component)
    327         .onLine(25);
    328   }
    329 
    330   @Test
    331   public void falsePositiveCyclicDependencyIndirectionDetected() {
    332     JavaFileObject component =
    333         JavaFileObjects.forSourceLines(
    334             "test.Outer",
    335             "package test;",
    336             "",
    337             "import dagger.Component;",
    338             "import dagger.Module;",
    339             "import dagger.Provides;",
    340             "import javax.inject.Inject;",
    341             "import javax.inject.Provider;",
    342             "",
    343             "final class Outer {",
    344             "  static class A {",
    345             "    @Inject A(C cParam) {}",
    346             "  }",
    347             "",
    348             "  static class B {",
    349             "    @Inject B(A aParam) {}",
    350             "  }",
    351             "",
    352             "  static class C {",
    353             "    @Inject C(B bParam) {}",
    354             "  }",
    355             "",
    356             "  static class D {",
    357             "    @Inject D(Provider<C> cParam) {}",
    358             "  }",
    359             "",
    360             "  @Component()",
    361             "  interface DComponent {",
    362             "    D getD();",
    363             "  }",
    364             "}");
    365 
    366     String expectedError =
    367         "test.Outer.DComponent.getD() contains a dependency cycle:\n"
    368             + "      test.Outer.D.<init>(javax.inject.Provider<test.Outer.C> cParam)\n"
    369             + "          [parameter: javax.inject.Provider<test.Outer.C> cParam]\n"
    370             + "      test.Outer.C.<init>(test.Outer.B bParam)\n"
    371             + "          [parameter: test.Outer.B bParam]\n"
    372             + "      test.Outer.B.<init>(test.Outer.A aParam)\n"
    373             + "          [parameter: test.Outer.A aParam]\n"
    374             + "      test.Outer.A.<init>(test.Outer.C cParam)\n"
    375             + "          [parameter: test.Outer.C cParam]";
    376 
    377     assertAbout(javaSource())
    378         .that(component)
    379         .processedWith(new ComponentProcessor())
    380         .failsToCompile()
    381         .withErrorContaining(expectedError)
    382         .in(component)
    383         .onLine(28);
    384   }
    385 
    386   @Ignore @Test public void cyclicDependencySimpleProviderIndirectionWarning() {
    387     JavaFileObject component =
    388         JavaFileObjects.forSourceLines(
    389             "test.Outer",
    390             "package test;",
    391             "",
    392             "import dagger.Component;",
    393             "import dagger.Module;",
    394             "import dagger.Provides;",
    395             "import javax.inject.Inject;",
    396             "import javax.inject.Provider;",
    397             "",
    398             "final class Outer {",
    399             "  static class A {",
    400             "    @Inject A(B bParam) {}",
    401             "  }",
    402             "",
    403             "  static class B {",
    404             "    @Inject B(C bParam, D dParam) {}",
    405             "  }",
    406             "",
    407             "  static class C {",
    408             "    @Inject C(Provider<A> aParam) {}",
    409             "  }",
    410             "",
    411             "  static class D {",
    412             "    @Inject D() {}",
    413             "  }",
    414             "",
    415             "  @Component()",
    416             "  interface CComponent {",
    417             "    C get();",
    418             "  }",
    419             "}");
    420 
    421     /* String expectedWarning =
    422      "test.Outer.CComponent.get() contains a dependency cycle:"
    423      + "      test.Outer.C.<init>(javax.inject.Provider<test.Outer.A> aParam)"
    424      + "          [parameter: javax.inject.Provider<test.Outer.A> aParam]"
    425      + "      test.Outer.A.<init>(test.Outer.B bParam)"
    426      + "          [parameter: test.Outer.B bParam]"
    427      + "      test.Outer.B.<init>(test.Outer.C bParam, test.Outer.D dParam)"
    428      + "          [parameter: test.Outer.C bParam]";
    429      */
    430     assertAbout(javaSource()) // TODO(cgruber): Implement warning checks.
    431         .that(component)
    432         .processedWith(new ComponentProcessor())
    433         .compilesWithoutError();
    434         //.withWarningContaining(expectedWarning).in(component).onLine(X);
    435   }
    436 
    437   @Ignore @Test public void cyclicDependencySimpleProviderIndirectionWarningSuppressed() {
    438     JavaFileObject component =
    439         JavaFileObjects.forSourceLines(
    440             "test.Outer",
    441             "package test;",
    442             "",
    443             "import dagger.Component;",
    444             "import dagger.Module;",
    445             "import dagger.Provides;",
    446             "import javax.inject.Inject;",
    447             "import javax.inject.Provider;",
    448             "",
    449             "final class Outer {",
    450             "  static class A {",
    451             "    @Inject A(B bParam) {}",
    452             "  }",
    453             "",
    454             "  static class B {",
    455             "    @Inject B(C bParam, D dParam) {}",
    456             "  }",
    457             "",
    458             "  static class C {",
    459             "    @Inject C(Provider<A> aParam) {}",
    460             "  }",
    461             "",
    462             "  static class D {",
    463             "    @Inject D() {}",
    464             "  }",
    465             "",
    466             "  @SuppressWarnings(\"dependency-cycle\")",
    467             "  @Component()",
    468             "  interface CComponent {",
    469             "    C get();",
    470             "  }",
    471             "}");
    472 
    473     assertAbout(javaSource())
    474         .that(component)
    475         .processedWith(new ComponentProcessor())
    476         .compilesWithoutError();
    477         //.compilesWithoutWarning(); //TODO(cgruber)
    478   }
    479 
    480   @Test public void duplicateExplicitBindings_ProvidesAndComponentProvision() {
    481     JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
    482         "package test;",
    483         "",
    484         "import dagger.Component;",
    485         "import dagger.Module;",
    486         "import dagger.Provides;",
    487         "",
    488         "final class Outer {",
    489         "  interface A {}",
    490         "",
    491         "  interface B {}",
    492         "",
    493         "  @Module",
    494         "  static class AModule {",
    495         "    @Provides String provideString() { return \"\"; }",
    496         "    @Provides A provideA(String s) { return new A() {}; }",
    497         "  }",
    498         "",
    499         "  @Component(modules = AModule.class)",
    500         "  interface Parent {",
    501         "    A getA();",
    502         "  }",
    503         "",
    504         "  @Module",
    505         "  static class BModule {",
    506         "    @Provides B provideB(A a) { return new B() {}; }",
    507         "  }",
    508         "",
    509         "  @Component(dependencies = Parent.class, modules = { BModule.class, AModule.class})",
    510         "  interface Child {",
    511         "    B getB();",
    512         "  }",
    513         "}");
    514 
    515     String expectedError = "test.Outer.A is bound multiple times:\n"
    516         + "      test.Outer.A test.Outer.Parent.getA()\n"
    517         + "      @Provides test.Outer.A test.Outer.AModule.provideA(String)";
    518 
    519     assertAbout(javaSource()).that(component)
    520         .processedWith(new ComponentProcessor())
    521         .failsToCompile()
    522         .withErrorContaining(expectedError).in(component).onLine(30);
    523   }
    524 
    525   @Test public void duplicateExplicitBindings_TwoProvidesMethods() {
    526     JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
    527         "package test;",
    528         "",
    529         "import dagger.Component;",
    530         "import dagger.Module;",
    531         "import dagger.Provides;",
    532         "import javax.inject.Inject;",
    533         "",
    534         "final class Outer {",
    535         "  interface A {}",
    536         "",
    537         "  @Module",
    538         "  static class Module1 {",
    539         "    @Provides A provideA1() { return new A() {}; }",
    540         "  }",
    541         "",
    542         "  @Module",
    543         "  static class Module2 {",
    544         "    @Provides String provideString() { return \"\"; }",
    545         "    @Provides A provideA2(String s) { return new A() {}; }",
    546         "  }",
    547         "",
    548         "  @Component(modules = { Module1.class, Module2.class})",
    549         "  interface TestComponent {",
    550         "    A getA();",
    551         "  }",
    552         "}");
    553 
    554     String expectedError = "test.Outer.A is bound multiple times:\n"
    555         + "      @Provides test.Outer.A test.Outer.Module1.provideA1()\n"
    556         + "      @Provides test.Outer.A test.Outer.Module2.provideA2(String)";
    557 
    558     assertAbout(javaSource()).that(component)
    559         .processedWith(new ComponentProcessor())
    560         .failsToCompile()
    561         .withErrorContaining(expectedError).in(component).onLine(24);
    562   }
    563 
    564   @Test public void duplicateExplicitBindings_MultipleProvisionTypes() {
    565     JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
    566         "package test;",
    567         "",
    568         "import dagger.Component;",
    569         "import dagger.MapKey;",
    570         "import dagger.Module;",
    571         "import dagger.Provides;",
    572         "import dagger.MapKey;",
    573         "import java.util.HashMap;",
    574         "import java.util.HashSet;",
    575         "import java.util.Map;",
    576         "import java.util.Set;",
    577         "",
    578         "import static java.lang.annotation.RetentionPolicy.RUNTIME;",
    579         "import static dagger.Provides.Type.MAP;",
    580         "import static dagger.Provides.Type.SET;",
    581         "",
    582         "final class Outer {",
    583         "  @MapKey(unwrapValue = true)",
    584         "  @interface StringKey {",
    585         "    String value();",
    586         "  }",
    587         "",
    588         "  @Module",
    589         "  static class TestModule1 {",
    590         "    @Provides(type = MAP)",
    591         "    @StringKey(\"foo\")",
    592         "    String stringMapEntry() { return \"\"; }",
    593         "",
    594         "    @Provides(type = SET) String stringSetElement() { return \"\"; }",
    595         "  }",
    596         "",
    597         "  @Module",
    598         "  static class TestModule2 {",
    599         "    @Provides Set<String> stringSet() { return new HashSet<String>(); }",
    600         "",
    601         "    @Provides Map<String, String> stringMap() {",
    602         "      return new HashMap<String, String>();",
    603         "    }",
    604         "  }",
    605         "",
    606         "  @Component(modules = { TestModule1.class, TestModule2.class })",
    607         "  interface TestComponent {",
    608         "    Set<String> getStringSet();",
    609         "    Map<String, String> getStringMap();",
    610         "  }",
    611         "}");
    612 
    613     String expectedSetError =
    614         "java.util.Set<java.lang.String> has incompatible bindings:\n"
    615             + "      Set bindings:\n"
    616             + "          @Provides(type=SET) String test.Outer.TestModule1.stringSetElement()\n"
    617             + "      Unique bindings:\n"
    618             + "          @Provides Set<String> test.Outer.TestModule2.stringSet()";
    619 
    620     String expectedMapError =
    621         "java.util.Map<java.lang.String,java.lang.String> has incompatible bindings:\n"
    622             + "      Map bindings:\n"
    623             + "          @Provides(type=MAP) @test.Outer.StringKey(\"foo\") String"
    624             + " test.Outer.TestModule1.stringMapEntry()\n"
    625             + "      Unique bindings:\n"
    626             + "          @Provides Map<String,String> test.Outer.TestModule2.stringMap()";
    627 
    628     assertAbout(javaSource()).that(component)
    629         .processedWith(new ComponentProcessor())
    630         .failsToCompile()
    631         .withErrorContaining(expectedSetError).in(component).onLine(43)
    632         .and().withErrorContaining(expectedMapError).in(component).onLine(44);
    633   }
    634 
    635   @Test public void duplicateBindings_TruncateAfterLimit() {
    636     JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
    637         "package test;",
    638         "",
    639         "import dagger.Component;",
    640         "import dagger.Module;",
    641         "import dagger.Provides;",
    642         "import javax.inject.Inject;",
    643         "",
    644         "final class Outer {",
    645         "  interface A {}",
    646         "",
    647         "  @Module",
    648         "  static class Module1 {",
    649         "    @Provides A provideA() { return new A() {}; }",
    650         "  }",
    651         "",
    652         "  @Module",
    653         "  static class Module2 {",
    654         "    @Provides A provideA() { return new A() {}; }",
    655         "  }",
    656         "",
    657         "  @Module",
    658         "  static class Module3 {",
    659         "    @Provides A provideA() { return new A() {}; }",
    660         "  }",
    661         "",
    662         "  @Module",
    663         "  static class Module4 {",
    664         "    @Provides A provideA() { return new A() {}; }",
    665         "  }",
    666         "",
    667         "  @Module",
    668         "  static class Module5 {",
    669         "    @Provides A provideA() { return new A() {}; }",
    670         "  }",
    671         "",
    672         "  @Module",
    673         "  static class Module6 {",
    674         "    @Provides A provideA() { return new A() {}; }",
    675         "  }",
    676         "",
    677         "  @Module",
    678         "  static class Module7 {",
    679         "    @Provides A provideA() { return new A() {}; }",
    680         "  }",
    681         "",
    682         "  @Module",
    683         "  static class Module8 {",
    684         "    @Provides A provideA() { return new A() {}; }",
    685         "  }",
    686         "",
    687         "  @Module",
    688         "  static class Module9 {",
    689         "    @Provides A provideA() { return new A() {}; }",
    690         "  }",
    691         "",
    692         "  @Module",
    693         "  static class Module10 {",
    694         "    @Provides A provideA() { return new A() {}; }",
    695         "  }",
    696         "",
    697         "  @Module",
    698         "  static class Module11 {",
    699         "    @Provides A provideA() { return new A() {}; }",
    700         "  }",
    701         "",
    702         "  @Module",
    703         "  static class Module12 {",
    704         "    @Provides A provideA() { return new A() {}; }",
    705         "  }",
    706         "",
    707         "  @Component(modules = {",
    708         "    Module1.class,",
    709         "    Module2.class,",
    710         "    Module3.class,",
    711         "    Module4.class,",
    712         "    Module5.class,",
    713         "    Module6.class,",
    714         "    Module7.class,",
    715         "    Module8.class,",
    716         "    Module9.class,",
    717         "    Module10.class,",
    718         "    Module11.class,",
    719         "    Module12.class",
    720         "  })",
    721         "  interface TestComponent {",
    722         "    A getA();",
    723         "  }",
    724         "}");
    725 
    726     String expectedError = "test.Outer.A is bound multiple times:\n"
    727         + "      @Provides test.Outer.A test.Outer.Module1.provideA()\n"
    728         + "      @Provides test.Outer.A test.Outer.Module2.provideA()\n"
    729         + "      @Provides test.Outer.A test.Outer.Module3.provideA()\n"
    730         + "      @Provides test.Outer.A test.Outer.Module4.provideA()\n"
    731         + "      @Provides test.Outer.A test.Outer.Module5.provideA()\n"
    732         + "      @Provides test.Outer.A test.Outer.Module6.provideA()\n"
    733         + "      @Provides test.Outer.A test.Outer.Module7.provideA()\n"
    734         + "      @Provides test.Outer.A test.Outer.Module8.provideA()\n"
    735         + "      @Provides test.Outer.A test.Outer.Module9.provideA()\n"
    736         + "      @Provides test.Outer.A test.Outer.Module10.provideA()\n"
    737         + "      and 2 others";
    738 
    739     assertAbout(javaSource()).that(component)
    740         .processedWith(new ComponentProcessor())
    741         .failsToCompile()
    742         .withErrorContaining(expectedError).in(component).onLine(86);
    743   }
    744 
    745   @Test public void longChainOfDependencies() {
    746     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass",
    747         "package test;",
    748         "",
    749         "import dagger.Component;",
    750         "import dagger.Module;",
    751         "import dagger.Provides;",
    752         "import javax.inject.Inject;",
    753         "",
    754         "final class TestClass {",
    755         "  interface A {}",
    756         "",
    757         "  static class B {",
    758         "    @Inject B(A a) {}",
    759         "  }",
    760         "",
    761         "  static class C {",
    762         "    @Inject B b;",
    763         "    @Inject C(B b) {}",
    764         "  }",
    765         "",
    766         "  interface D { }",
    767         "",
    768         "  static class DImpl implements D {",
    769         "    @Inject DImpl(C c, B b) {}",
    770         "  }",
    771         "",
    772         "  @Module",
    773         "  static class DModule {",
    774         "    @Provides D d(DImpl impl) { return impl; }",
    775         "  }",
    776         "",
    777         "  @Component(modules = { DModule.class })",
    778         "  interface AComponent {",
    779         "    D getFoo();",
    780         "    C injectC(C c);",
    781         "  }",
    782         "}");
    783     String errorText =
    784         "test.TestClass.A cannot be provided without an @Provides-annotated method.\n";
    785     String firstError = errorText
    786         + "      test.TestClass.DModule.d(test.TestClass.DImpl impl)\n"
    787         + "          [parameter: test.TestClass.DImpl impl]\n"
    788         + "      test.TestClass.DImpl.<init>(test.TestClass.C c, test.TestClass.B b)\n"
    789         + "          [parameter: test.TestClass.C c]\n"
    790         + "      test.TestClass.C.b\n"
    791         + "          [injected field of type: test.TestClass.B b]\n"
    792         + "      test.TestClass.B.<init>(test.TestClass.A a)\n"
    793         + "          [parameter: test.TestClass.A a]";
    794     String secondError = errorText
    795         + "      test.TestClass.C.b\n"
    796         + "          [injected field of type: test.TestClass.B b]\n"
    797         + "      test.TestClass.B.<init>(test.TestClass.A a)\n"
    798         + "          [parameter: test.TestClass.A a]";
    799     assertAbout(javaSource()).that(component)
    800         .processedWith(new ComponentProcessor())
    801         .failsToCompile()
    802         .withErrorContaining(firstError).in(component).onLine(33)
    803         .and().withErrorContaining(secondError).in(component).onLine(34);
    804   }
    805 
    806   @Test public void resolvedParametersInDependencyTrace() {
    807     JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic",
    808         "package test;",
    809         "",
    810         "import javax.inject.Inject;",
    811         "import javax.inject.Provider;",
    812         "",
    813         "final class Generic<T> {",
    814         "  @Inject Generic(T t) {}",
    815         "}");
    816     JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass",
    817         "package test;",
    818         "",
    819         "import javax.inject.Inject;",
    820         "import java.util.List;",
    821         "",
    822         "final class TestClass {",
    823         "  @Inject TestClass(List list) {}",
    824         "}");
    825     JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest",
    826         "package test;",
    827         "",
    828         "import javax.inject.Inject;",
    829         "",
    830         "final class UsesTest {",
    831         "  @Inject UsesTest(Generic<TestClass> genericTestClass) {}",
    832         "}");
    833     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
    834         "package test;",
    835         "",
    836         "import dagger.Component;",
    837         "",
    838         "@Component",
    839         "interface TestComponent {",
    840         "  UsesTest usesTest();",
    841         "}");
    842     String expectedMsg = Joiner.on("\n").join(
    843         "java.util.List cannot be provided without an @Provides-annotated method.",
    844         "      test.UsesTest.<init>(test.Generic<test.TestClass> genericTestClass)",
    845         "          [parameter: test.Generic<test.TestClass> genericTestClass]",
    846         "      test.Generic.<init>(test.TestClass t)",
    847         "          [parameter: test.TestClass t]",
    848         "      test.TestClass.<init>(java.util.List list)",
    849         "          [parameter: java.util.List list]");
    850     assertAbout(javaSources()).that(ImmutableList.of(generic, testClass, usesTest, component))
    851         .processedWith(new ComponentProcessor())
    852         .failsToCompile()
    853         .withErrorContaining(expectedMsg);
    854   }
    855 
    856   @Test public void resolvedVariablesInDependencyTrace() {
    857     JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic",
    858         "package test;",
    859         "",
    860         "import javax.inject.Inject;",
    861         "import javax.inject.Provider;",
    862         "",
    863         "final class Generic<T> {",
    864         "  @Inject T t;",
    865         "  @Inject Generic() {}",
    866         "}");
    867     JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass",
    868         "package test;",
    869         "",
    870         "import javax.inject.Inject;",
    871         "import java.util.List;",
    872         "",
    873         "final class TestClass {",
    874         "  @Inject TestClass(List list) {}",
    875         "}");
    876     JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest",
    877         "package test;",
    878         "",
    879         "import javax.inject.Inject;",
    880         "",
    881         "final class UsesTest {",
    882         "  @Inject UsesTest(Generic<TestClass> genericTestClass) {}",
    883         "}");
    884     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
    885         "package test;",
    886         "",
    887         "import dagger.Component;",
    888         "",
    889         "@Component",
    890         "interface TestComponent {",
    891         "  UsesTest usesTest();",
    892         "}");
    893     String expectedMsg = Joiner.on("\n").join(
    894         "java.util.List cannot be provided without an @Provides-annotated method.",
    895         "      test.UsesTest.<init>(test.Generic<test.TestClass> genericTestClass)",
    896         "          [parameter: test.Generic<test.TestClass> genericTestClass]",
    897         "      test.Generic.t",
    898         "          [injected field of type: test.TestClass t]",
    899         "      test.TestClass.<init>(java.util.List list)",
    900         "          [parameter: java.util.List list]");
    901     assertAbout(javaSources()).that(ImmutableList.of(generic, testClass, usesTest, component))
    902         .processedWith(new ComponentProcessor())
    903         .failsToCompile()
    904         .withErrorContaining(expectedMsg);
    905   }
    906 
    907   @Test public void nullCheckForConstructorParameters() {
    908     JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
    909         "package test;",
    910         "",
    911         "import javax.inject.Inject;",
    912         "",
    913         "final class A {",
    914         "  @Inject A(String string) {}",
    915         "}");
    916     JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
    917         "package test;",
    918         "",
    919         "import dagger.Provides;",
    920         "import javax.inject.Inject;",
    921         "",
    922         "@dagger.Module",
    923         "final class TestModule {",
    924         "  @Nullable @Provides String provideString() { return null; }",
    925         "}");
    926     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
    927         "package test;",
    928         "",
    929         "import dagger.Component;",
    930         "",
    931         "@Component(modules = TestModule.class)",
    932         "interface TestComponent {",
    933         "  A a();",
    934         "}");
    935     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
    936         .processedWith(new ComponentProcessor())
    937         .failsToCompile()
    938         .withErrorContaining(
    939             nullableToNonNullable(
    940                 "java.lang.String",
    941                 "@test.Nullable @Provides String test.TestModule.provideString()"));
    942 
    943     // but if we disable the validation, then it compiles fine.
    944     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
    945         .withCompilerOptions("-Adagger.nullableValidation=WARNING")
    946         .processedWith(new ComponentProcessor())
    947         .compilesWithoutError();
    948   }
    949 
    950   @Test public void nullCheckForMembersInjectParam() {
    951     JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
    952         "package test;",
    953         "",
    954         "import javax.inject.Inject;",
    955         "",
    956         "final class A {",
    957         "  @Inject A() {}",
    958         "  @Inject void register(String string) {}",
    959         "}");
    960     JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
    961         "package test;",
    962         "",
    963         "import dagger.Provides;",
    964         "import javax.inject.Inject;",
    965         "",
    966         "@dagger.Module",
    967         "final class TestModule {",
    968         "  @Nullable @Provides String provideString() { return null; }",
    969         "}");
    970     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
    971         "package test;",
    972         "",
    973         "import dagger.Component;",
    974         "",
    975         "@Component(modules = TestModule.class)",
    976         "interface TestComponent {",
    977         "  A a();",
    978         "}");
    979     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
    980         .processedWith(new ComponentProcessor())
    981         .failsToCompile()
    982         .withErrorContaining(
    983             nullableToNonNullable(
    984                 "java.lang.String",
    985                 "@test.Nullable @Provides String test.TestModule.provideString()"));
    986 
    987     // but if we disable the validation, then it compiles fine.
    988     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
    989         .withCompilerOptions("-Adagger.nullableValidation=WARNING")
    990         .processedWith(new ComponentProcessor())
    991         .compilesWithoutError();
    992   }
    993 
    994   @Test public void nullCheckForVariable() {
    995     JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
    996         "package test;",
    997         "",
    998         "import javax.inject.Inject;",
    999         "",
   1000         "final class A {",
   1001         "  @Inject String string;",
   1002         "  @Inject A() {}",
   1003         "}");
   1004     JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
   1005         "package test;",
   1006         "",
   1007         "import dagger.Provides;",
   1008         "import javax.inject.Inject;",
   1009         "",
   1010         "@dagger.Module",
   1011         "final class TestModule {",
   1012         "  @Nullable @Provides String provideString() { return null; }",
   1013         "}");
   1014     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
   1015         "package test;",
   1016         "",
   1017         "import dagger.Component;",
   1018         "",
   1019         "@Component(modules = TestModule.class)",
   1020         "interface TestComponent {",
   1021         "  A a();",
   1022         "}");
   1023     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
   1024         .processedWith(new ComponentProcessor())
   1025         .failsToCompile()
   1026         .withErrorContaining(
   1027             nullableToNonNullable(
   1028                 "java.lang.String",
   1029                 "@test.Nullable @Provides String test.TestModule.provideString()"));
   1030 
   1031     // but if we disable the validation, then it compiles fine.
   1032     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component))
   1033         .withCompilerOptions("-Adagger.nullableValidation=WARNING")
   1034         .processedWith(new ComponentProcessor())
   1035         .compilesWithoutError();
   1036   }
   1037 
   1038   @Test public void nullCheckForComponentReturn() {
   1039     JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
   1040         "package test;",
   1041         "",
   1042         "import dagger.Provides;",
   1043         "import javax.inject.Inject;",
   1044         "",
   1045         "@dagger.Module",
   1046         "final class TestModule {",
   1047         "  @Nullable @Provides String provideString() { return null; }",
   1048         "}");
   1049     JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
   1050         "package test;",
   1051         "",
   1052         "import dagger.Component;",
   1053         "",
   1054         "@Component(modules = TestModule.class)",
   1055         "interface TestComponent {",
   1056         "  String string();",
   1057         "}");
   1058     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component))
   1059         .processedWith(new ComponentProcessor())
   1060         .failsToCompile()
   1061         .withErrorContaining(
   1062             nullableToNonNullable(
   1063                 "java.lang.String",
   1064                 "@test.Nullable @Provides String test.TestModule.provideString()"));
   1065 
   1066     // but if we disable the validation, then it compiles fine.
   1067     assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component))
   1068         .withCompilerOptions("-Adagger.nullableValidation=WARNING")
   1069         .processedWith(new ComponentProcessor())
   1070         .compilesWithoutError();
   1071   }
   1072 
   1073   @Test public void componentDependencyMustNotCycle_Direct() {
   1074     JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort",
   1075         "package test;",
   1076         "",
   1077         "import dagger.Component;",
   1078         "",
   1079         "@Component(dependencies = ComponentShort.class)",
   1080         "interface ComponentShort {",
   1081         "}");
   1082     String errorMessage =
   1083         "test.ComponentShort contains a cycle in its component dependencies:\n"
   1084             + "      test.ComponentShort";
   1085     assertAbout(javaSource())
   1086         .that(shortLifetime)
   1087         .processedWith(new ComponentProcessor())
   1088         .failsToCompile()
   1089         .withErrorContaining(errorMessage);
   1090   }
   1091 
   1092   @Test public void componentDependencyMustNotCycle_Indirect() {
   1093     JavaFileObject longLifetime = JavaFileObjects.forSourceLines("test.ComponentLong",
   1094         "package test;",
   1095         "",
   1096         "import dagger.Component;",
   1097         "",
   1098         "@Component(dependencies = ComponentMedium.class)",
   1099         "interface ComponentLong {",
   1100         "}");
   1101     JavaFileObject mediumLifetime = JavaFileObjects.forSourceLines("test.ComponentMedium",
   1102         "package test;",
   1103         "",
   1104         "import dagger.Component;",
   1105         "",
   1106         "@Component(dependencies = ComponentLong.class)",
   1107         "interface ComponentMedium {",
   1108         "}");
   1109     JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort",
   1110         "package test;",
   1111         "",
   1112         "import dagger.Component;",
   1113         "",
   1114         "@Component(dependencies = ComponentMedium.class)",
   1115         "interface ComponentShort {",
   1116         "}");
   1117     String longErrorMessage =
   1118         "test.ComponentLong contains a cycle in its component dependencies:\n"
   1119             + "      test.ComponentLong\n"
   1120             + "      test.ComponentMedium\n"
   1121             + "      test.ComponentLong";
   1122     String mediumErrorMessage =
   1123         "test.ComponentMedium contains a cycle in its component dependencies:\n"
   1124             + "      test.ComponentMedium\n"
   1125             + "      test.ComponentLong\n"
   1126             + "      test.ComponentMedium";
   1127     String shortErrorMessage =
   1128         "test.ComponentShort contains a cycle in its component dependencies:\n"
   1129             + "      test.ComponentMedium\n"
   1130             + "      test.ComponentLong\n"
   1131             + "      test.ComponentMedium\n"
   1132             + "      test.ComponentShort";
   1133     assertAbout(javaSources())
   1134         .that(ImmutableList.of(longLifetime, mediumLifetime, shortLifetime))
   1135         .processedWith(new ComponentProcessor())
   1136         .failsToCompile()
   1137         .withErrorContaining(longErrorMessage).in(longLifetime)
   1138         .and()
   1139         .withErrorContaining(mediumErrorMessage).in(mediumLifetime)
   1140         .and()
   1141         .withErrorContaining(shortErrorMessage).in(shortLifetime);
   1142   }
   1143 }
   1144