• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
22 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
23 import static java.lang.annotation.RetentionPolicy.RUNTIME;
24 
25 import com.google.common.collect.ImmutableList;
26 import com.google.testing.compile.Compilation;
27 import com.google.testing.compile.JavaFileObjects;
28 import dagger.Module;
29 import dagger.multibindings.IntKey;
30 import dagger.multibindings.LongKey;
31 import dagger.producers.ProducerModule;
32 import java.lang.annotation.Annotation;
33 import java.lang.annotation.Retention;
34 import java.util.Collection;
35 import javax.inject.Qualifier;
36 import javax.tools.JavaFileObject;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.junit.runners.Parameterized;
40 import org.junit.runners.Parameterized.Parameters;
41 
42 @RunWith(Parameterized.class)
43 public class BindsMethodValidationTest {
44   @Parameters
data()45   public static Collection<Object[]> data() {
46     return ImmutableList.copyOf(new Object[][] {{Module.class}, {ProducerModule.class}});
47   }
48 
49   private final String moduleAnnotation;
50   private final String moduleDeclaration;
51 
BindsMethodValidationTest(Class<? extends Annotation> moduleAnnotation)52   public BindsMethodValidationTest(Class<? extends Annotation> moduleAnnotation) {
53     this.moduleAnnotation = "@" + moduleAnnotation.getCanonicalName();
54     moduleDeclaration = this.moduleAnnotation + " abstract class %s { %s }";
55   }
56 
57   @Test
nonAbstract()58   public void nonAbstract() {
59     assertThatMethod("@Binds Object concrete(String impl) { return null; }")
60         .hasError("must be abstract");
61   }
62 
63   @Test
notAssignable()64   public void notAssignable() {
65     assertThatMethod("@Binds abstract String notAssignable(Object impl);").hasError("assignable");
66   }
67 
68   @Test
moreThanOneParameter()69   public void moreThanOneParameter() {
70     assertThatMethod("@Binds abstract Object tooManyParameters(String s1, String s2);")
71         .hasError("one parameter");
72   }
73 
74   @Test
typeParameters()75   public void typeParameters() {
76     assertThatMethod("@Binds abstract <S, T extends S> S generic(T t);")
77         .hasError("type parameters");
78   }
79 
80   @Test
notInModule()81   public void notInModule() {
82     assertThatMethodInUnannotatedClass("@Binds abstract Object bindObject(String s);")
83         .hasError("within a @Module or @ProducerModule");
84   }
85 
86   @Test
throwsException()87   public void throwsException() {
88     assertThatMethod("@Binds abstract Object throwsException(String s1) throws RuntimeException;")
89         .hasError("may not throw");
90   }
91 
92   @Test
returnsVoid()93   public void returnsVoid() {
94     assertThatMethod("@Binds abstract void returnsVoid(Object impl);").hasError("void");
95   }
96 
97   @Test
tooManyQualifiersOnMethod()98   public void tooManyQualifiersOnMethod() {
99     assertThatMethod(
100             "@Binds @Qualifier1 @Qualifier2 abstract String tooManyQualifiers(String impl);")
101         .importing(Qualifier1.class, Qualifier2.class)
102         .hasError("more than one @Qualifier");
103   }
104 
105   @Test
tooManyQualifiersOnParameter()106   public void tooManyQualifiersOnParameter() {
107     assertThatMethod(
108             "@Binds abstract String tooManyQualifiers(@Qualifier1 @Qualifier2 String impl);")
109         .importing(Qualifier1.class, Qualifier2.class)
110         .hasError("more than one @Qualifier");
111   }
112 
113   @Test
noParameters()114   public void noParameters() {
115     assertThatMethod("@Binds abstract Object noParameters();").hasError("one parameter");
116   }
117 
118   @Test
setElementsNotAssignable()119   public void setElementsNotAssignable() {
120     assertThatMethod(
121             "@Binds @ElementsIntoSet abstract Set<String> bindSetOfIntegers(Set<Integer> ints);")
122         .hasError("assignable");
123   }
124 
125   @Test
setElements_primitiveArgument()126   public void setElements_primitiveArgument() {
127     assertThatMethod("@Binds @ElementsIntoSet abstract Set<Number> bindInt(int integer);")
128         .hasError("assignable");
129   }
130 
131   @Test
elementsIntoSet_withRawSets()132   public void elementsIntoSet_withRawSets() {
133     assertThatMethod("@Binds @ElementsIntoSet abstract Set bindRawSet(HashSet hashSet);")
134         .hasError("cannot return a raw Set");
135   }
136 
137   @Test
intoMap_noMapKey()138   public void intoMap_noMapKey() {
139     assertThatMethod("@Binds @IntoMap abstract Object bindNoMapKey(String string);")
140         .hasError("methods of type map must declare a map key");
141   }
142 
143   @Test
intoMap_multipleMapKeys()144   public void intoMap_multipleMapKeys() {
145     assertThatMethod(
146             "@Binds @IntoMap @IntKey(1) @LongKey(2L) abstract Object manyMapKeys(String string);")
147         .importing(IntKey.class, LongKey.class)
148         .hasError("may not have more than one map key");
149   }
150 
151   @Test
bindsMissingTypeInParameterHierarchy()152   public void bindsMissingTypeInParameterHierarchy() {
153     JavaFileObject module =
154         JavaFileObjects.forSourceLines(
155             "test.TestComponent",
156             "package test;",
157             "",
158             "import dagger.Binds;",
159             "",
160             moduleAnnotation,
161             "interface TestModule {",
162             "  @Binds String bindObject(Child<String> child);",
163             "}");
164 
165     JavaFileObject child =
166         JavaFileObjects.forSourceLines(
167             "test.Child",
168             "package test;",
169             "",
170             "class Child<T> extends Parent<T> {}");
171 
172     JavaFileObject parent =
173         JavaFileObjects.forSourceLines(
174             "test.Parent",
175             "package test;",
176             "",
177             "class Parent<T> extends MissingType {}");
178 
179     Compilation compilation = daggerCompiler().compile(module, child, parent);
180     assertThat(compilation).failed();
181     assertThat(compilation).hadErrorCount(3);
182     assertThat(compilation)
183         .hadErrorContaining(
184             "cannot find symbol"
185                 + "\n  symbol: class MissingType");
186     assertThat(compilation)
187         .hadErrorContaining(
188             "ModuleProcessingStep was unable to process 'test.TestModule' because 'MissingType' "
189                 + "could not be resolved.");
190     assertThat(compilation)
191         .hadErrorContaining(
192             "BindingMethodProcessingStep was unable to process"
193                 + " 'bindObject(test.Child<java.lang.String>)' because 'MissingType' could not be"
194                 + " resolved."
195                 + "\n  "
196                 + "\n  Dependency trace:"
197                 + "\n      => element (INTERFACE): test.TestModule"
198                 + "\n      => element (METHOD): bindObject(test.Child<java.lang.String>)"
199                 + "\n      => element (PARAMETER): child"
200                 + "\n      => type (DECLARED parameter): test.Child<java.lang.String>"
201                 + "\n      => type (DECLARED supertype): test.Parent<java.lang.String>"
202                 + "\n      => type (ERROR supertype): MissingType");
203   }
204 
205 
206   @Test
bindsMissingTypeInReturnTypeHierarchy()207   public void bindsMissingTypeInReturnTypeHierarchy() {
208     JavaFileObject module =
209         JavaFileObjects.forSourceLines(
210             "test.TestComponent",
211             "package test;",
212             "",
213             "import dagger.Binds;",
214             "",
215             moduleAnnotation,
216             "interface TestModule {",
217             "  @Binds Child<String> bindChild(String str);",
218             "}");
219 
220     JavaFileObject child =
221         JavaFileObjects.forSourceLines(
222             "test.Child",
223             "package test;",
224             "",
225             "class Child<T> extends Parent<T> {}");
226 
227     JavaFileObject parent =
228         JavaFileObjects.forSourceLines(
229             "test.Parent",
230             "package test;",
231             "",
232             "class Parent<T> extends MissingType {}");
233 
234     Compilation compilation = daggerCompiler().compile(module, child, parent);
235     assertThat(compilation).failed();
236     assertThat(compilation).hadErrorCount(3);
237     assertThat(compilation)
238         .hadErrorContaining(
239             "cannot find symbol"
240                 + "\n  symbol: class MissingType");
241     assertThat(compilation)
242         .hadErrorContaining(
243             "ModuleProcessingStep was unable to process 'test.TestModule' because 'MissingType' "
244                 + "could not be resolved.");
245     assertThat(compilation)
246         .hadErrorContaining(
247             "BindingMethodProcessingStep was unable to process 'bindChild(java.lang.String)'"
248                 + " because 'MissingType' could not be resolved."
249                 + "\n  "
250                 + "\n  Dependency trace:"
251                 + "\n      => element (INTERFACE): test.TestModule"
252                 + "\n      => element (METHOD): bindChild(java.lang.String)"
253                 + "\n      => type (DECLARED return type): test.Child<java.lang.String>"
254                 + "\n      => type (DECLARED supertype): test.Parent<java.lang.String>"
255                 + "\n      => type (ERROR supertype): MissingType");
256   }
257 
assertThatMethod(String method)258   private DaggerModuleMethodSubject assertThatMethod(String method) {
259     return assertThatModuleMethod(method).withDeclaration(moduleDeclaration);
260   }
261 
262   @Qualifier
263   @Retention(RUNTIME)
264   public @interface Qualifier1 {}
265 
266   @Qualifier
267   @Retention(RUNTIME)
268   public @interface Qualifier2 {}
269 }
270