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