• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
componentOnConcreteClass()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 
componentProvisionWithNoDependencyChain()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 
constructorInjectionWithoutAnnotation()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 
membersInjectWithoutProvision()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 
cyclicDependency()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 
cyclicDependencyNotIncludingEntryPoint()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
cyclicDependencyNotBrokenByMapBinding()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
falsePositiveCyclicDependencyIndirectionDetected()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 
cyclicDependencySimpleProviderIndirectionWarning()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 
cyclicDependencySimpleProviderIndirectionWarningSuppressed()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 
duplicateExplicitBindings_ProvidesAndComponentProvision()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 
duplicateExplicitBindings_TwoProvidesMethods()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 
duplicateExplicitBindings_MultipleProvisionTypes()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 
duplicateBindings_TruncateAfterLimit()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 
longChainOfDependencies()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 
resolvedParametersInDependencyTrace()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 
resolvedVariablesInDependencyTrace()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 
nullCheckForConstructorParameters()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 
nullCheckForMembersInjectParam()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 
nullCheckForVariable()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 
nullCheckForComponentReturn()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 
componentDependencyMustNotCycle_Direct()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 
componentDependencyMustNotCycle_Indirect()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