• 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 androidx.room.compiler.processing.util.Source;
20 import com.google.common.base.Joiner;
21 import dagger.testing.compile.CompilerTests;
22 import dagger.testing.compile.CompilerTests.DaggerCompiler;
23 import org.junit.Test;
24 import org.junit.runner.RunWith;
25 import org.junit.runners.JUnit4;
26 
27 @RunWith(JUnit4.class)
28 public class AndroidMapKeyValidatorTest {
29   private static final Source FOO_ACTIVITY =
30       CompilerTests.javaSource(
31           "test.FooActivity",
32           "package test;",
33           "",
34           "import android.app.Activity;",
35           "import dagger.android.AndroidInjector;",
36           "",
37           "public class FooActivity extends Activity {",
38           "  interface Factory extends AndroidInjector.Factory<FooActivity> {}",
39           "  abstract static class Builder extends AndroidInjector.Builder<FooActivity> {}",
40           "}");
41   private static final Source BAR_ACTIVITY =
42       CompilerTests.javaSource(
43           "test.BarActivity",
44           "package test;",
45           "",
46           "import android.app.Activity;",
47           "",
48           "public class BarActivity extends Activity {}");
49 
moduleWithMethod(String... lines)50   private static Source moduleWithMethod(String... lines) {
51     return CompilerTests.javaSource(
52         "test.AndroidModule",
53         "package test;",
54         "",
55         "import android.app.Activity;",
56         "import android.app.Fragment;",
57         "import dagger.Module;",
58         "import dagger.*;",
59         "import dagger.android.*;",
60         "import dagger.multibindings.*;",
61         "import javax.inject.*;",
62         "",
63         "@Module",
64         "abstract class AndroidModule {",
65         "  " + Joiner.on("\n  ").join(lines),
66         "}");
67   }
68 
69   // TODO(dpb): Change these tests to use onLineContaining() instead of onLine().
70   private static final int LINES_BEFORE_METHOD = 12;
71 
72   @Test
rawFactoryType()73   public void rawFactoryType() {
74     Source module =
75         moduleWithMethod(
76             "@Binds",
77             "@IntoMap",
78             "@ClassKey(FooActivity.class)",
79             "abstract AndroidInjector.Factory bindRawFactory(FooActivity.Factory factory);");
80     compile(module, FOO_ACTIVITY)
81         .compile(
82             subject -> {
83               subject.compilationDidFail();
84               subject.hasErrorContaining(
85                   "should bind dagger.android.AndroidInjector.Factory<?>, "
86                       + "not dagger.android.AndroidInjector.Factory");
87             });
88   }
89 
90   @Test
wildCardFactoryType()91   public void wildCardFactoryType() {
92     Source module =
93         CompilerTests.kotlinSource(
94             "AndroidModule.kt",
95             "package test",
96             "",
97             "import dagger.Module",
98             "import dagger.Binds",
99             "import dagger.android.AndroidInjector",
100             "import dagger.multibindings.ClassKey",
101             "import dagger.multibindings.IntoMap",
102             "",
103             "@Module",
104             "internal abstract class AndroidModule {",
105             "   @Binds",
106             "   @IntoMap",
107             "   @ClassKey(FooActivity::class)",
108             "   abstract fun bindWildcardFactory(factory: FooActivity.Factory):"
109                 + " AndroidInjector.Factory<*>",
110             "}");
111     compile(module, FOO_ACTIVITY).compile(subject -> subject.hasErrorCount(0));
112   }
113 
114   @Test
rawBuilderType()115   public void rawBuilderType() {
116     Source module =
117         moduleWithMethod(
118             "@Binds",
119             "@IntoMap",
120             "@ClassKey(FooActivity.class)",
121             "abstract AndroidInjector.Builder bindRawBuilder(FooActivity.Builder builder);");
122     compile(module, FOO_ACTIVITY)
123         .compile(
124             subject -> {
125               subject.compilationDidFail();
126               subject.hasErrorContaining(
127                   "should bind dagger.android.AndroidInjector.Factory<?>, "
128                       + "not dagger.android.AndroidInjector.Builder");
129             });
130   }
131 
132   @Test
bindsToBuilderNotFactory()133   public void bindsToBuilderNotFactory() {
134     Source module =
135         moduleWithMethod(
136             "@Binds",
137             "@IntoMap",
138             "@ClassKey(FooActivity.class)",
139             "abstract AndroidInjector.Builder<?> bindBuilder(",
140             "    FooActivity.Builder builder);");
141     compile(module, FOO_ACTIVITY)
142         .compile(
143             subject -> {
144               subject.compilationDidFail();
145               subject.hasErrorContaining(
146                   "should bind dagger.android.AndroidInjector.Factory<?>, not "
147                       + "dagger.android.AndroidInjector.Builder<?>");
148             });
149   }
150 
151   @Test
providesToBuilderNotFactory()152   public void providesToBuilderNotFactory() {
153     Source module =
154         moduleWithMethod(
155             "@Provides",
156             "@IntoMap",
157             "@ClassKey(FooActivity.class)",
158             "static AndroidInjector.Builder<?> bindBuilder(FooActivity.Builder builder) {",
159             "  return builder;",
160             "}");
161     compile(module, FOO_ACTIVITY)
162         .compile(
163             subject -> {
164               subject.compilationDidFail();
165               subject.hasErrorContaining(
166                   "should bind dagger.android.AndroidInjector.Factory<?>, not "
167                       + "dagger.android.AndroidInjector.Builder<?>");
168             });
169   }
170 
171   @Test
bindsToConcreteTypeInsteadOfWildcard()172   public void bindsToConcreteTypeInsteadOfWildcard() {
173     Source module =
174         moduleWithMethod(
175             "@Binds",
176             "@IntoMap",
177             "@ClassKey(FooActivity.class)",
178             "abstract AndroidInjector.Builder<FooActivity> bindBuilder(",
179             "    FooActivity.Builder builder);");
180     compile(module, FOO_ACTIVITY)
181         .compile(
182             subject -> {
183               subject.compilationDidFail();
184               subject.hasErrorContaining(
185                   "should bind dagger.android.AndroidInjector.Factory<?>, not "
186                       + "dagger.android.AndroidInjector.Builder<test.FooActivity>");
187             });
188   }
189 
190   @Test
bindsToBaseTypeInsteadOfWildcard()191   public void bindsToBaseTypeInsteadOfWildcard() {
192     Source module =
193         moduleWithMethod(
194             "@Binds",
195             "@IntoMap",
196             "@ClassKey(FooActivity.class)",
197             "abstract AndroidInjector.Builder<Activity> bindBuilder(",
198             "    FooActivity.Builder builder);");
199     compile(module, FOO_ACTIVITY)
200         .compile(
201             subject -> {
202               subject.compilationDidFail();
203               subject.hasErrorContaining(
204                   "@Binds methods' parameter type must be assignable to the return type");
205             });
206   }
207 
208   @Test
bindsCorrectType()209   public void bindsCorrectType() {
210     Source module =
211         moduleWithMethod(
212             "@Binds",
213             "@IntoMap",
214             "@ClassKey(FooActivity.class)",
215             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
216     compile(module, FOO_ACTIVITY)
217         .compile(
218             subject -> {
219               subject.hasErrorCount(0);
220               subject.hasNoWarnings();
221             });
222   }
223 
224   @Test
bindsCorrectType_AndroidInjectionKey()225   public void bindsCorrectType_AndroidInjectionKey() {
226     Source module =
227         moduleWithMethod(
228             "@Binds",
229             "@IntoMap",
230             "@AndroidInjectionKey(\"test.FooActivity\")",
231             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
232     compile(module, FOO_ACTIVITY)
233         .compile(
234             subject -> {
235               subject.hasErrorCount(0);
236               subject.hasNoWarnings();
237             });
238   }
239 
240   @Test
bindsCorrectType_AndroidInjectionKey_unbounded()241   public void bindsCorrectType_AndroidInjectionKey_unbounded() {
242     Source module =
243         moduleWithMethod(
244             "@Binds",
245             "@IntoMap",
246             "@AndroidInjectionKey(\"test.FooActivity\")",
247             "abstract AndroidInjector.Factory<?> bindCorrectType(FooActivity.Builder builder);");
248     compile(module, FOO_ACTIVITY)
249         .compile(
250             subject -> {
251               subject.hasErrorCount(0);
252               subject.hasNoWarnings();
253             });
254   }
255 
256   @Test
bindsWithScope()257   public void bindsWithScope() {
258     Source module =
259         moduleWithMethod(
260             "@Binds",
261             "@IntoMap",
262             "@ClassKey(FooActivity.class)",
263             "@Singleton",
264             "abstract AndroidInjector.Factory<?> bindWithScope(FooActivity.Builder builder);");
265     compile(module, FOO_ACTIVITY)
266         .compile(
267             subject -> {
268               subject.compilationDidFail();
269               subject.hasErrorContaining("should not be scoped");
270             });
271   }
272 
273   @Test
bindsWithScope_suppressWarnings()274   public void bindsWithScope_suppressWarnings() {
275     Source module =
276         moduleWithMethod(
277             "@SuppressWarnings(\"dagger.android.ScopedInjectorFactory\")",
278             "@Binds",
279             "@IntoMap",
280             "@ClassKey(FooActivity.class)",
281             "@Singleton",
282             "abstract AndroidInjector.Factory<?> bindWithScope(FooActivity.Builder builder);");
283     compile(module, FOO_ACTIVITY)
284         .compile(
285             subject -> {
286               subject.hasErrorCount(0);
287               subject.hasNoWarnings();
288             });
289   }
290 
291   @Test
mismatchedMapKey_bindsFactory()292   public void mismatchedMapKey_bindsFactory() {
293     Source module =
294         moduleWithMethod(
295             "@Binds",
296             "@IntoMap",
297             "@ClassKey(BarActivity.class)",
298             "abstract AndroidInjector.Factory<?> mismatchedFactory(FooActivity.Factory factory);");
299     compile(module, FOO_ACTIVITY, BAR_ACTIVITY)
300         .compile(
301             subject -> {
302               subject.compilationDidFail();
303               subject
304                   .hasErrorContaining(
305                       "test.FooActivity.Factory does not implement"
306                           + " AndroidInjector<test.BarActivity>")
307                   .onLine(LINES_BEFORE_METHOD + 3);
308             });
309   }
310 
311   @Test
mismatchedMapKey_bindsBuilder()312   public void mismatchedMapKey_bindsBuilder() {
313     Source module =
314         moduleWithMethod(
315             "@Binds",
316             "@IntoMap",
317             "@ClassKey(BarActivity.class)",
318             "abstract AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder);");
319     compile(module, FOO_ACTIVITY, BAR_ACTIVITY)
320         .compile(
321             subject -> {
322               subject.compilationDidFail();
323               subject
324                   .hasErrorContaining(
325                       "test.FooActivity.Builder does not implement"
326                           + " AndroidInjector<test.BarActivity>")
327                   .onLine(LINES_BEFORE_METHOD + 3);
328             });
329   }
330 
331   @Test
mismatchedMapKey_bindsBuilder_androidInjectionKey()332   public void mismatchedMapKey_bindsBuilder_androidInjectionKey() {
333     Source module =
334         moduleWithMethod(
335             "@Binds",
336             "@IntoMap",
337             "@AndroidInjectionKey(\"test.BarActivity\")",
338             "abstract AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder);");
339     compile(module, FOO_ACTIVITY, BAR_ACTIVITY)
340         .compile(
341             subject -> {
342               subject.compilationDidFail();
343               subject
344                   .hasErrorContaining(
345                       "test.FooActivity.Builder does not implement"
346                           + " AndroidInjector<test.BarActivity>")
347                   .onLine(LINES_BEFORE_METHOD + 3);
348             });
349   }
350 
351   @Test
mismatchedMapKey_providesBuilder()352   public void mismatchedMapKey_providesBuilder() {
353     Source module =
354         moduleWithMethod(
355             "@Provides",
356             "@IntoMap",
357             "@ClassKey(BarActivity.class)",
358             "static AndroidInjector.Factory<?> mismatchedBuilder(FooActivity.Builder builder) {",
359             "  return builder;",
360             "}");
361     compile(module, FOO_ACTIVITY, BAR_ACTIVITY)
362         .compile(
363             subject -> {
364               subject.hasErrorCount(0);
365               subject.hasNoWarnings();
366             });
367   }
368 
369   @Test
bindsQualifier_ignoresChecks()370   public void bindsQualifier_ignoresChecks() {
371     Source module =
372         moduleWithMethod(
373             "@Binds",
374             "@IntoMap",
375             "@ClassKey(FooActivity.class)",
376             "@Named(\"unused\")",
377             // normally this should fail, since it is binding to a Builder not a Factory
378             "abstract AndroidInjector.Builder<?> bindsBuilderWithQualifier(",
379             "    FooActivity.Builder builder);");
380     compile(module, FOO_ACTIVITY)
381         .compile(
382             subject -> {
383               subject.hasErrorCount(0);
384               subject.hasNoWarnings();
385             });
386   }
387 
388   @Test
bindToPrimitive()389   public void bindToPrimitive() {
390     Source module =
391         moduleWithMethod(
392             "@Binds",
393             "@IntoMap",
394             "@AndroidInjectionKey(\"test.FooActivity\")",
395             "abstract int bindInt(@Named(\"unused\") int otherInt);");
396     compile(module, FOO_ACTIVITY)
397         .compile(
398             subject -> {
399               subject.hasErrorCount(0);
400               subject.hasNoWarnings();
401             });
402   }
403 
404   @Test
bindToNonFrameworkClass()405   public void bindToNonFrameworkClass() {
406     Source module =
407         moduleWithMethod(
408             "@Binds",
409             "@IntoMap",
410             "@AndroidInjectionKey(\"test.FooActivity\")",
411             "abstract Number bindInt(Integer integer);");
412     compile(module, FOO_ACTIVITY)
413         .compile(
414             subject -> {
415               subject.hasErrorCount(0);
416               subject.hasNoWarnings();
417             });
418   }
419 
420   @Test
invalidBindsMethod()421   public void invalidBindsMethod() {
422     Source module =
423         moduleWithMethod(
424             "@Binds",
425             "@IntoMap",
426             "@ClassKey(FooActivity.class)",
427             "abstract AndroidInjector.Factory<?> bindCorrectType(",
428             "    FooActivity.Builder builder, FooActivity.Builder builder2);");
429     compile(module, FOO_ACTIVITY).compile(subject -> subject.compilationDidFail());
430   }
431 
compile(Source... files)432   private DaggerCompiler compile(Source... files) {
433     return CompilerTests.daggerCompiler(files)
434         .withAdditionalJavacProcessors(new AndroidProcessor())
435         .withAdditionalKspProcessors(new KspAndroidProcessor.Provider());
436   }
437 }
438