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