1 /*
2  * Copyright (C) 2015 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen;
18 
19 import static com.google.testing.compile.CompilationSubject.assertThat;
20 import static dagger.internal.codegen.Compilers.daggerCompiler;
21 import static dagger.internal.codegen.TestUtils.message;
22 
23 import com.google.testing.compile.Compilation;
24 import com.google.testing.compile.JavaFileObjects;
25 import javax.tools.JavaFileObject;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.JUnit4;
29 
30 @RunWith(JUnit4.class)
31 public class MissingBindingSuggestionsTest {
injectable(String className, String constructorParams)32   private static JavaFileObject injectable(String className, String constructorParams) {
33     return JavaFileObjects.forSourceLines("test." + className,
34         "package test;",
35         "",
36         "import javax.inject.Inject;",
37         "",
38         "class " + className +" {",
39         "  @Inject " + className + "(" + constructorParams + ") {}",
40         "}");
41   }
42 
emptyInterface(String interfaceName)43   private static JavaFileObject emptyInterface(String interfaceName) {
44     return JavaFileObjects.forSourceLines("test." + interfaceName,
45         "package test;",
46         "",
47         "import javax.inject.Inject;",
48         "",
49         "interface " + interfaceName +" {}");
50   }
51 
suggestsBindingInSeparateComponent()52   @Test public void suggestsBindingInSeparateComponent() {
53     JavaFileObject fooComponent = JavaFileObjects.forSourceLines("test.FooComponent",
54         "package test;",
55         "",
56         "import dagger.Subcomponent;",
57         "",
58         "@Subcomponent",
59         "interface FooComponent {",
60         "  Foo getFoo();",
61         "}");
62     JavaFileObject barModule = JavaFileObjects.forSourceLines("test.BarModule",
63         "package test;",
64         "",
65         "import dagger.Provides;",
66         "import javax.inject.Inject;",
67         "",
68         "@dagger.Module",
69         "final class BarModule {",
70         "  @Provides Bar provideBar() {return null;}",
71         "}");
72     JavaFileObject barComponent = JavaFileObjects.forSourceLines("test.BarComponent",
73         "package test;",
74         "",
75         "import dagger.Subcomponent;",
76         "",
77         "@Subcomponent(modules = {BarModule.class})",
78         "interface BarComponent {",
79         "  Bar getBar();",
80         "}");
81     JavaFileObject foo = injectable("Foo", "Bar bar");
82     JavaFileObject bar = emptyInterface("Bar");
83 
84     JavaFileObject topComponent = JavaFileObjects.forSourceLines("test.TopComponent",
85         "package test;",
86         "",
87         "import dagger.Component;",
88         "",
89         "@Component",
90         "interface TopComponent {",
91         "  FooComponent getFoo();",
92         "  BarComponent getBar(BarModule barModule);",
93         "}");
94 
95     Compilation compilation =
96         daggerCompiler().compile(fooComponent, barComponent, topComponent, foo, bar, barModule);
97     assertThat(compilation).failed();
98     assertThat(compilation).hadErrorCount(1);
99     assertThat(compilation)
100         .hadErrorContaining("A binding with matching key exists in component: BarComponent");
101   }
102 
suggestsBindingInNestedSubcomponent()103   @Test public void suggestsBindingInNestedSubcomponent() {
104     JavaFileObject fooComponent = JavaFileObjects.forSourceLines("test.FooComponent",
105         "package test;",
106         "",
107         "import dagger.Subcomponent;",
108         "",
109         "@Subcomponent",
110         "interface FooComponent {",
111         "  Foo getFoo();",
112         "}");
113     JavaFileObject barComponent = JavaFileObjects.forSourceLines("test.BarComponent",
114         "package test;",
115         "",
116         "import dagger.Subcomponent;",
117         "",
118         "@Subcomponent()",
119         "interface BarComponent {",
120         "  BazComponent getBaz();",
121         "}");
122     JavaFileObject bazModule = JavaFileObjects.forSourceLines("test.BazModule",
123         "package test;",
124         "",
125         "import dagger.Provides;",
126         "import javax.inject.Inject;",
127         "",
128         "@dagger.Module",
129         "final class BazModule {",
130         "  @Provides Baz provideBaz() {return null;}",
131         "}");
132     JavaFileObject bazComponent = JavaFileObjects.forSourceLines("test.BazComponent",
133         "package test;",
134         "",
135         "import dagger.Subcomponent;",
136         "",
137         "@Subcomponent(modules = {BazModule.class})",
138         "interface BazComponent {",
139         "  Baz getBaz();",
140         "}");
141     JavaFileObject foo = injectable("Foo", "Baz baz");
142     JavaFileObject baz = emptyInterface("Baz");
143 
144     JavaFileObject topComponent = JavaFileObjects.forSourceLines("test.TopComponent",
145         "package test;",
146         "",
147         "import dagger.Component;",
148         "",
149         "@Component",
150         "interface TopComponent {",
151         "  FooComponent getFoo();",
152         "  BarComponent getBar();",
153         "}");
154 
155     Compilation compilation =
156         daggerCompiler()
157             .compile(fooComponent, barComponent, bazComponent, topComponent, foo, baz, bazModule);
158     assertThat(compilation).failed();
159     assertThat(compilation).hadErrorCount(1);
160     assertThat(compilation)
161         .hadErrorContaining("A binding with matching key exists in component: BazComponent");
162   }
163 
164   @Test
missingBindingInParentComponent()165   public void missingBindingInParentComponent() {
166     JavaFileObject parent =
167         JavaFileObjects.forSourceLines(
168             "Parent",
169             "import dagger.Component;",
170             "",
171             "@Component",
172             "interface Parent {",
173             "  Foo foo();",
174             "  Bar bar();",
175             "  Child child();",
176             "}");
177     JavaFileObject child =
178         JavaFileObjects.forSourceLines(
179             "Child",
180             "import dagger.Subcomponent;",
181             "",
182             "@Subcomponent(modules=BazModule.class)",
183             "interface Child {",
184             "  Foo foo();",
185             "  Baz baz();",
186             "}");
187     JavaFileObject foo =
188         JavaFileObjects.forSourceLines(
189             "Foo",
190             "import javax.inject.Inject;",
191             "",
192             "class Foo {",
193             "  @Inject Foo(Bar bar) {}",
194             "}");
195     JavaFileObject bar =
196         JavaFileObjects.forSourceLines(
197             "Bar",
198             "import javax.inject.Inject;",
199             "",
200             "class Bar {",
201             "  @Inject Bar(Baz baz) {}",
202             "}");
203     JavaFileObject baz = JavaFileObjects.forSourceLines("Baz", "class Baz {}");
204     JavaFileObject bazModule = JavaFileObjects.forSourceLines(
205         "BazModule",
206         "import dagger.Module;",
207         "import dagger.Provides;",
208         "import javax.inject.Inject;",
209         "",
210         "@Module",
211         "final class BazModule {",
212         "  @Provides Baz provideBaz() {return new Baz();}",
213         "}");
214 
215     Compilation compilation = daggerCompiler().compile(parent, child, foo, bar, baz, bazModule);
216     assertThat(compilation).failed();
217     assertThat(compilation).hadErrorCount(1);
218     assertThat(compilation)
219         .hadErrorContaining(
220             message(
221                 "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an "
222                     + "@Inject constructor or an @Provides-annotated method.",
223                 "A binding with matching key exists in component: Child",
224                 "    Baz is injected at",
225                 "        Bar(baz)",
226                 "    Bar is requested at",
227                 "        Parent.bar()",
228                 "The following other entry points also depend on it:",
229                 "    Parent.foo()",
230                 "    Child.foo() [Parent → Child]"))
231         .inFile(parent)
232         .onLineContaining("interface Parent");
233   }
234 
235   @Test
missingBindingInSiblingComponent()236   public void missingBindingInSiblingComponent() {
237     JavaFileObject parent =
238         JavaFileObjects.forSourceLines(
239             "Parent",
240             "import dagger.Component;",
241             "",
242             "@Component",
243             "interface Parent {",
244             "  Foo foo();",
245             "  Bar bar();",
246             "  Child1 child1();",
247             "  Child2 child2();",
248             "}");
249     JavaFileObject child1 =
250         JavaFileObjects.forSourceLines(
251             "Child1",
252             "import dagger.Subcomponent;",
253             "",
254             "@Subcomponent",
255             "interface Child1 {",
256             "  Foo foo();",
257             "  Baz baz();",
258             "}");
259     JavaFileObject child2 =
260         JavaFileObjects.forSourceLines(
261             "Child2",
262             "import dagger.Subcomponent;",
263             "",
264             "@Subcomponent(modules = BazModule.class)",
265             "interface Child2 {",
266             "  Foo foo();",
267             "  Baz baz();",
268             "}");
269     JavaFileObject foo =
270         JavaFileObjects.forSourceLines(
271             "Foo",
272             "import javax.inject.Inject;",
273             "",
274             "class Foo {",
275             "  @Inject Foo(Bar bar) {}",
276             "}");
277     JavaFileObject bar =
278         JavaFileObjects.forSourceLines(
279             "Bar",
280             "import javax.inject.Inject;",
281             "",
282             "class Bar {",
283             "  @Inject Bar(Baz baz) {}",
284             "}");
285     JavaFileObject baz = JavaFileObjects.forSourceLines("Baz", "class Baz {}");
286     JavaFileObject bazModule = JavaFileObjects.forSourceLines(
287         "BazModule",
288         "import dagger.Module;",
289         "import dagger.Provides;",
290         "import javax.inject.Inject;",
291         "",
292         "@Module",
293         "final class BazModule {",
294         "  @Provides Baz provideBaz() {return new Baz();}",
295         "}");
296 
297     Compilation compilation =
298         daggerCompiler().compile(parent, child1, child2, foo, bar, baz, bazModule);
299     assertThat(compilation).failed();
300     assertThat(compilation).hadErrorCount(1);
301     assertThat(compilation)
302         .hadErrorContaining(
303             message(
304                 "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an "
305                     + "@Inject constructor or an @Provides-annotated method.",
306                 "A binding with matching key exists in component: Child2",
307                 "    Baz is injected at",
308                 "        Bar(baz)",
309                 "    Bar is requested at",
310                 "        Parent.bar()",
311                 "The following other entry points also depend on it:",
312                 "    Parent.foo()",
313                 "    Child1.foo() [Parent → Child1]",
314                 "    Child2.foo() [Parent → Child2]",
315                 "    Child1.baz() [Parent → Child1]"))
316         .inFile(parent)
317         .onLineContaining("interface Parent");
318   }
319 }
320