• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.android.processor;
18 
19 import static com.google.testing.compile.CompilationSubject.assertThat;
20 import static com.google.testing.compile.Compiler.javac;
21 
22 import com.google.common.base.Joiner;
23 import com.google.testing.compile.Compilation;
24 import com.google.testing.compile.JavaFileObjects;
25 import dagger.internal.codegen.ComponentProcessor;
26 import javax.tools.JavaFileObject;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.junit.runners.JUnit4;
30 
31 @RunWith(JUnit4.class)
32 public class AndroidMapKeyValidatorTest {
33   private static final JavaFileObject FOO_ACTIVITY =
34       JavaFileObjects.forSourceLines(
35           "test.FooActivity",
36           "package test;",
37           "",
38           "import android.app.Activity;",
39           "import dagger.android.AndroidInjector;",
40           "",
41           "public class FooActivity extends Activity {",
42           "  interface Factory extends AndroidInjector.Factory<FooActivity> {}",
43           "  abstract static class Builder extends AndroidInjector.Builder<FooActivity> {}",
44           "}");
45   private static final JavaFileObject BAR_ACTIVITY =
46       JavaFileObjects.forSourceLines(
47           "test.BarActivity",
48           "package test;",
49           "",
50           "import android.app.Activity;",
51           "",
52           "public class BarActivity extends Activity {}");
53 
moduleWithMethod(String... lines)54   private static JavaFileObject moduleWithMethod(String... lines) {
55     return JavaFileObjects.forSourceLines(
56         "test.AndroidModule",
57         "package test;",
58         "",
59         "import android.app.Activity;",
60         "import android.app.Fragment;",
61         "import dagger.Module;",
62         "import dagger.*;",
63         "import dagger.android.*;",
64         "import dagger.multibindings.*;",
65         "import javax.inject.*;",
66         "",
67         "@Module",
68         "abstract class AndroidModule {",
69         "  " + Joiner.on("\n  ").join(lines),
70         "}");
71   }
72 
73   // TODO(dpb): Change these tests to use onLineContaining() instead of onLine().
74   private static final int LINES_BEFORE_METHOD = 12;
75 
76   @Test
rawFactoryType()77   public void rawFactoryType() {
78     JavaFileObject module =
79         moduleWithMethod(
80             "@Binds",
81             "@IntoMap",
82             "@ClassKey(FooActivity.class)",
83             "abstract AndroidInjector.Factory bindRawFactory(FooActivity.Factory factory);");
84     Compilation compilation = compile(module, FOO_ACTIVITY);
85     assertThat(compilation).failed();
86     assertThat(compilation)
87         .hadErrorContaining(
88             "should bind dagger.android.AndroidInjector.Factory<?>, "
89                 + "not dagger.android.AndroidInjector.Factory");
90   }
91 
92   @Test
rawBuilderType()93   public void rawBuilderType() {
94     JavaFileObject module =
95         moduleWithMethod(
96             "@Binds",
97             "@IntoMap",
98             "@ClassKey(FooActivity.class)",
99             "abstract AndroidInjector.Builder bindRawBuilder(FooActivity.Builder builder);");
100     Compilation compilation = compile(module, FOO_ACTIVITY);
101     assertThat(compilation).failed();
102     assertThat(compilation)
103         .hadErrorContaining(
104             "should bind dagger.android.AndroidInjector.Factory<?>, "
105                 + "not dagger.android.AndroidInjector.Builder");
106   }
107 
108   @Test
bindsToBuilderNotFactory()109   public void bindsToBuilderNotFactory() {
110     JavaFileObject module =
111         moduleWithMethod(
112             "@Binds",
113             "@IntoMap",
114             "@ClassKey(FooActivity.class)",
115             "abstract AndroidInjector.Builder<?> bindBuilder(",
116             "    FooActivity.Builder builder);");
117     Compilation compilation = compile(module, FOO_ACTIVITY);
118     assertThat(compilation).failed();
119     assertThat(compilation)
120         .hadErrorContaining(
121             "should bind dagger.android.AndroidInjector.Factory<?>, not "
122                 + "dagger.android.AndroidInjector.Builder<?>");
123   }
124 
125   @Test
providesToBuilderNotFactory()126   public void providesToBuilderNotFactory() {
127     JavaFileObject module =
128         moduleWithMethod(
129             "@Provides",
130             "@IntoMap",
131             "@ClassKey(FooActivity.class)",
132             "static AndroidInjector.Builder<?> bindBuilder(FooActivity.Builder builder) {",
133             "  return builder;",
134             "}");
135     Compilation compilation = compile(module, FOO_ACTIVITY);
136     assertThat(compilation).failed();
137     assertThat(compilation)
138         .hadErrorContaining(
139             "should bind dagger.android.AndroidInjector.Factory<?>, not "
140                 + "dagger.android.AndroidInjector.Builder<?>");
141   }
142 
143   @Test
bindsToConcreteTypeInsteadOfWildcard()144   public void bindsToConcreteTypeInsteadOfWildcard() {
145     JavaFileObject module =
146         moduleWithMethod(
147             "@Binds",
148             "@IntoMap",
149             "@ClassKey(FooActivity.class)",
150             "abstract AndroidInjector.Builder<FooActivity> bindBuilder(",
151             "    FooActivity.Builder builder);");
152     Compilation compilation = compile(module, FOO_ACTIVITY);
153     assertThat(compilation).failed();
154     assertThat(compilation)
155         .hadErrorContaining(
156             "should bind dagger.android.AndroidInjector.Factory<?>, not "
157                 + "dagger.android.AndroidInjector.Builder<test.FooActivity>");
158   }
159 
160   @Test
bindsToBaseTypeInsteadOfWildcard()161   public void bindsToBaseTypeInsteadOfWildcard() {
162     JavaFileObject module =
163         moduleWithMethod(
164             "@Binds",
165             "@IntoMap",
166             "@ClassKey(FooActivity.class)",
167             "abstract AndroidInjector.Builder<Activity> bindBuilder(",
168             "    FooActivity.Builder builder);");
169     Compilation compilation = compile(module, FOO_ACTIVITY);
170     assertThat(compilation).failed();
171     assertThat(compilation)
172         .hadErrorContaining("@Binds methods' parameter type must be assignable to the return type");
173   }
174 
175   @Test
bindsCorrectType()176   public void bindsCorrectType() {
177     JavaFileObject module =
178         moduleWithMethod(
179             "@Binds",
180             "@IntoMap",
181             "@ClassKey(FooActivity.class)",
182             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
183     Compilation compilation = compile(module, FOO_ACTIVITY);
184     assertThat(compilation).succeededWithoutWarnings();
185   }
186 
187   @Test
bindsCorrectType_AndroidInjectionKey()188   public void bindsCorrectType_AndroidInjectionKey() {
189     JavaFileObject module =
190         moduleWithMethod(
191             "@Binds",
192             "@IntoMap",
193             "@AndroidInjectionKey(\"test.FooActivity\")",
194             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
195     Compilation compilation = compile(module, FOO_ACTIVITY);
196     assertThat(compilation).succeededWithoutWarnings();
197   }
198 
199   @Test
bindsCorrectType_AndroidInjectionKey_unbounded()200   public void bindsCorrectType_AndroidInjectionKey_unbounded() {
201     JavaFileObject module =
202         moduleWithMethod(
203             "@Binds",
204             "@IntoMap",
205             "@AndroidInjectionKey(\"test.FooActivity\")",
206             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
207     Compilation compilation = compile(module, FOO_ACTIVITY);
208     assertThat(compilation).succeededWithoutWarnings();
209   }
210 
211   @Test
bindsWithScope()212   public void bindsWithScope() {
213     JavaFileObject module =
214         moduleWithMethod(
215             "@Binds",
216             "@IntoMap",
217             "@ClassKey(FooActivity.class)",
218             "@Singleton",
219             "abstract AndroidInjector.Factory<?> bindWithScope(FooActivity.Builder builder);");
220     Compilation compilation = compile(module, FOO_ACTIVITY);
221     assertThat(compilation).failed();
222     assertThat(compilation).hadErrorContaining("should not be scoped");
223   }
224 
225   @Test
bindsWithScope_suppressWarnings()226   public void bindsWithScope_suppressWarnings() {
227     JavaFileObject module =
228         moduleWithMethod(
229             "@SuppressWarnings(\"dagger.android.ScopedInjectorFactory\")",
230             "@Binds",
231             "@IntoMap",
232             "@ClassKey(FooActivity.class)",
233             "@Singleton",
234             "abstract AndroidInjector.Factory<?> bindWithScope(FooActivity.Builder builder);");
235     Compilation compilation = compile(module, FOO_ACTIVITY);
236     assertThat(compilation).succeededWithoutWarnings();
237   }
238 
239   @Test
mismatchedMapKey_bindsFactory()240   public void mismatchedMapKey_bindsFactory() {
241     JavaFileObject module =
242         moduleWithMethod(
243             "@Binds",
244             "@IntoMap",
245             "@ClassKey(BarActivity.class)",
246             "abstract AndroidInjector.Factory<?> mismatchedFactory(FooActivity.Factory factory);");
247     Compilation compilation = compile(module, FOO_ACTIVITY, BAR_ACTIVITY);
248     assertThat(compilation).failed();
249     assertThat(compilation)
250         .hadErrorContaining(
251             "test.FooActivity.Factory does not implement AndroidInjector<test.BarActivity>")
252         .inFile(module)
253         .onLine(LINES_BEFORE_METHOD + 3);
254   }
255 
256   @Test
mismatchedMapKey_bindsBuilder()257   public void mismatchedMapKey_bindsBuilder() {
258     JavaFileObject module =
259         moduleWithMethod(
260             "@Binds",
261             "@IntoMap",
262             "@ClassKey(BarActivity.class)",
263             "abstract AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder);");
264     Compilation compilation = compile(module, FOO_ACTIVITY, BAR_ACTIVITY);
265     assertThat(compilation).failed();
266     assertThat(compilation)
267         .hadErrorContaining(
268             "test.FooActivity.Builder does not implement AndroidInjector<test.BarActivity>")
269         .inFile(module)
270         .onLine(LINES_BEFORE_METHOD + 3);
271   }
272 
273   @Test
mismatchedMapKey_bindsBuilder_androidInjectionKey()274   public void mismatchedMapKey_bindsBuilder_androidInjectionKey() {
275     JavaFileObject module =
276         moduleWithMethod(
277             "@Binds",
278             "@IntoMap",
279             "@AndroidInjectionKey(\"test.BarActivity\")",
280             "abstract AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder);");
281     Compilation compilation = compile(module, FOO_ACTIVITY, BAR_ACTIVITY);
282     assertThat(compilation).failed();
283     assertThat(compilation)
284         .hadErrorContaining(
285             "test.FooActivity.Builder does not implement AndroidInjector<test.BarActivity>")
286         .inFile(module)
287         .onLine(LINES_BEFORE_METHOD + 3);
288   }
289 
290   @Test
mismatchedMapKey_providesBuilder()291   public void mismatchedMapKey_providesBuilder() {
292     JavaFileObject module =
293         moduleWithMethod(
294             "@Provides",
295             "@IntoMap",
296             "@ClassKey(BarActivity.class)",
297             "static AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder) {",
298             "  return builder;",
299             "}");
300     Compilation compilation = compile(module, FOO_ACTIVITY, BAR_ACTIVITY);
301     assertThat(compilation).succeededWithoutWarnings();
302   }
303 
304   @Test
bindsQualifier_ignoresChecks()305   public void bindsQualifier_ignoresChecks() {
306     JavaFileObject module =
307         moduleWithMethod(
308             "@Binds",
309             "@IntoMap",
310             "@ClassKey(FooActivity.class)",
311             "@Named(\"unused\")",
312             // normally this should fail, since it is binding to a Builder not a Factory
313             "abstract AndroidInjector.Builder<?> bindsBuilderWithQualifier(",
314             "    FooActivity.Builder builder);");
315     Compilation compilation = compile(module, FOO_ACTIVITY);
316     assertThat(compilation).succeededWithoutWarnings();
317   }
318 
319   @Test
bindToPrimitive()320   public void bindToPrimitive() {
321     JavaFileObject module =
322         moduleWithMethod(
323             "@Binds",
324             "@IntoMap",
325             "@AndroidInjectionKey(\"test.FooActivity\")",
326             "abstract int bindInt(@Named(\"unused\") int otherInt);");
327     Compilation compilation = compile(module, FOO_ACTIVITY);
328     assertThat(compilation).succeededWithoutWarnings();
329   }
330 
331   @Test
bindToNonFrameworkClass()332   public void bindToNonFrameworkClass() {
333     JavaFileObject module =
334         moduleWithMethod(
335             "@Binds",
336             "@IntoMap",
337             "@AndroidInjectionKey(\"test.FooActivity\")",
338             "abstract Number bindInt(Integer integer);");
339     Compilation compilation = compile(module, FOO_ACTIVITY);
340     assertThat(compilation).succeededWithoutWarnings();
341   }
342 
343   @Test
invalidBindsMethod()344   public void invalidBindsMethod() {
345     JavaFileObject module =
346         moduleWithMethod(
347             "@Binds",
348             "@IntoMap",
349             "@ClassKey(FooActivity.class)",
350             "abstract AndroidInjector.Factory<?> bindCorrectType(",
351             "    FooActivity.Builder builder, FooActivity.Builder builder2);");
352     Compilation compilation = compile(module, FOO_ACTIVITY);
353     assertThat(compilation).failed();
354   }
355 
compile(JavaFileObject... files)356   private Compilation compile(JavaFileObject... files) {
357     return javac().withProcessors(new ComponentProcessor(), new AndroidProcessor()).compile(files);
358   }
359 }
360