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.assertThatModuleMethod; 22 23 import com.google.testing.compile.Compilation; 24 import com.google.testing.compile.JavaFileObjects; 25 import dagger.Module; 26 import dagger.producers.ProducerModule; 27 import java.lang.annotation.Annotation; 28 import java.util.Arrays; 29 import java.util.Collection; 30 import javax.tools.JavaFileObject; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.junit.runners.Parameterized; 34 35 @RunWith(Parameterized.class) 36 public final class ModuleValidationTest { 37 38 @Parameterized.Parameters(name = "{0}") parameters()39 public static Collection<Object[]> parameters() { 40 return Arrays.asList(new Object[][] {{ModuleType.MODULE}, {ModuleType.PRODUCER_MODULE}}); 41 } 42 43 private enum ModuleType { 44 MODULE(Module.class), 45 PRODUCER_MODULE(ProducerModule.class), 46 ; 47 48 private final Class<? extends Annotation> annotation; 49 ModuleType(Class<? extends Annotation> annotation)50 ModuleType(Class<? extends Annotation> annotation) { 51 this.annotation = annotation; 52 } 53 annotationWithSubcomponent(String subcomponent)54 String annotationWithSubcomponent(String subcomponent) { 55 return String.format("@%s(subcomponents = %s)", annotation.getSimpleName(), subcomponent); 56 } 57 importStatement()58 String importStatement() { 59 return String.format("import %s;", annotation.getName()); 60 } 61 simpleName()62 String simpleName() { 63 return annotation.getSimpleName(); 64 } 65 } 66 67 private final ModuleType moduleType; 68 ModuleValidationTest(ModuleType moduleType)69 public ModuleValidationTest(ModuleType moduleType) { 70 this.moduleType = moduleType; 71 } 72 73 @Test moduleSubcomponents_notASubcomponent()74 public void moduleSubcomponents_notASubcomponent() { 75 JavaFileObject module = 76 JavaFileObjects.forSourceLines( 77 "test.TestModule", 78 "package test;", 79 "", 80 moduleType.importStatement(), 81 "", 82 moduleType.annotationWithSubcomponent("NotASubcomponent.class"), 83 "class TestModule {}"); 84 JavaFileObject notASubcomponent = 85 JavaFileObjects.forSourceLines( 86 "test.NotASubcomponent", "package test;", "", "class NotASubcomponent {}"); 87 Compilation compilation = daggerCompiler().compile(module, notASubcomponent); 88 assertThat(compilation).failed(); 89 assertThat(compilation) 90 .hadErrorContaining( 91 "test.NotASubcomponent is not a @Subcomponent or @ProductionSubcomponent") 92 .inFile(module) 93 .onLine(5); 94 } 95 96 @Test moduleSubcomponents_listsSubcomponentBuilder()97 public void moduleSubcomponents_listsSubcomponentBuilder() { 98 JavaFileObject module = 99 JavaFileObjects.forSourceLines( 100 "test.TestModule", 101 "package test;", 102 "", 103 moduleType.importStatement(), 104 "", 105 moduleType.annotationWithSubcomponent("Sub.Builder.class"), 106 "class TestModule {}"); 107 JavaFileObject subcomponent = 108 JavaFileObjects.forSourceLines( 109 "test.Sub", 110 "package test;", 111 "", 112 "import dagger.Subcomponent;", 113 "", 114 "@Subcomponent", 115 "interface Sub {", 116 " @Subcomponent.Builder", 117 " interface Builder {", 118 " Sub build();", 119 " }", 120 "}"); 121 Compilation compilation = daggerCompiler().compile(module, subcomponent); 122 assertThat(compilation).failed(); 123 assertThat(compilation) 124 .hadErrorContaining( 125 "test.Sub.Builder is a @Subcomponent.Builder. Did you mean to use test.Sub?") 126 .inFile(module) 127 .onLine(5); 128 } 129 130 @Test moduleSubcomponents_listsSubcomponentFactory()131 public void moduleSubcomponents_listsSubcomponentFactory() { 132 JavaFileObject module = 133 JavaFileObjects.forSourceLines( 134 "test.TestModule", 135 "package test;", 136 "", 137 moduleType.importStatement(), 138 "", 139 moduleType.annotationWithSubcomponent("Sub.Factory.class"), 140 "class TestModule {}"); 141 JavaFileObject subcomponent = 142 JavaFileObjects.forSourceLines( 143 "test.Sub", 144 "package test;", 145 "", 146 "import dagger.Subcomponent;", 147 "", 148 "@Subcomponent", 149 "interface Sub {", 150 " @Subcomponent.Factory", 151 " interface Factory {", 152 " Sub creator();", 153 " }", 154 "}"); 155 Compilation compilation = daggerCompiler().compile(module, subcomponent); 156 assertThat(compilation).failed(); 157 assertThat(compilation) 158 .hadErrorContaining( 159 "test.Sub.Factory is a @Subcomponent.Factory. Did you mean to use test.Sub?") 160 .inFile(module) 161 .onLine(5); 162 } 163 164 @Test moduleSubcomponents_listsProductionSubcomponentBuilder()165 public void moduleSubcomponents_listsProductionSubcomponentBuilder() { 166 JavaFileObject module = 167 JavaFileObjects.forSourceLines( 168 "test.TestModule", 169 "package test;", 170 "", 171 moduleType.importStatement(), 172 "", 173 moduleType.annotationWithSubcomponent("Sub.Builder.class"), 174 "class TestModule {}"); 175 JavaFileObject subcomponent = 176 JavaFileObjects.forSourceLines( 177 "test.Sub", 178 "package test;", 179 "", 180 "import dagger.producers.ProductionSubcomponent;", 181 "", 182 "@ProductionSubcomponent", 183 "interface Sub {", 184 " @ProductionSubcomponent.Builder", 185 " interface Builder {", 186 " Sub build();", 187 " }", 188 "}"); 189 Compilation compilation = daggerCompiler().compile(module, subcomponent); 190 assertThat(compilation).failed(); 191 assertThat(compilation) 192 .hadErrorContaining( 193 "test.Sub.Builder is a @ProductionSubcomponent.Builder. Did you mean to use test.Sub?") 194 .inFile(module) 195 .onLine(5); 196 } 197 198 @Test moduleSubcomponents_listsProductionSubcomponentFactory()199 public void moduleSubcomponents_listsProductionSubcomponentFactory() { 200 JavaFileObject module = 201 JavaFileObjects.forSourceLines( 202 "test.TestModule", 203 "package test;", 204 "", 205 moduleType.importStatement(), 206 "", 207 moduleType.annotationWithSubcomponent("Sub.Factory.class"), 208 "class TestModule {}"); 209 JavaFileObject subcomponent = 210 JavaFileObjects.forSourceLines( 211 "test.Sub", 212 "package test;", 213 "", 214 "import dagger.producers.ProductionSubcomponent;", 215 "", 216 "@ProductionSubcomponent", 217 "interface Sub {", 218 " @ProductionSubcomponent.Factory", 219 " interface Factory {", 220 " Sub create();", 221 " }", 222 "}"); 223 Compilation compilation = daggerCompiler().compile(module, subcomponent); 224 assertThat(compilation).failed(); 225 assertThat(compilation) 226 .hadErrorContaining( 227 "test.Sub.Factory is a @ProductionSubcomponent.Factory. Did you mean to use test.Sub?") 228 .inFile(module) 229 .onLine(5); 230 } 231 232 @Test moduleSubcomponents_noSubcomponentCreator()233 public void moduleSubcomponents_noSubcomponentCreator() { 234 JavaFileObject module = 235 JavaFileObjects.forSourceLines( 236 "test.TestModule", 237 "package test;", 238 "", 239 moduleType.importStatement(), 240 "", 241 moduleType.annotationWithSubcomponent("NoBuilder.class"), 242 "class TestModule {}"); 243 JavaFileObject subcomponent = 244 JavaFileObjects.forSourceLines( 245 "test.NoBuilder", 246 "package test;", 247 "", 248 "import dagger.Subcomponent;", 249 "", 250 "@Subcomponent", 251 "interface NoBuilder {}"); 252 Compilation compilation = daggerCompiler().compile(module, subcomponent); 253 assertThat(compilation).failed(); 254 assertThat(compilation) 255 .hadErrorContaining( 256 "test.NoBuilder doesn't have a @Subcomponent.Builder or @Subcomponent.Factory, which " 257 + "is required when used with @" 258 + moduleType.simpleName() 259 + ".subcomponents") 260 .inFile(module) 261 .onLine(5); 262 } 263 264 @Test moduleSubcomponents_noProductionSubcomponentCreator()265 public void moduleSubcomponents_noProductionSubcomponentCreator() { 266 JavaFileObject module = 267 JavaFileObjects.forSourceLines( 268 "test.TestModule", 269 "package test;", 270 "", 271 moduleType.importStatement(), 272 "", 273 moduleType.annotationWithSubcomponent("NoBuilder.class"), 274 "class TestModule {}"); 275 JavaFileObject subcomponent = 276 JavaFileObjects.forSourceLines( 277 "test.NoBuilder", 278 "package test;", 279 "", 280 "import dagger.producers.ProductionSubcomponent;", 281 "", 282 "@ProductionSubcomponent", 283 "interface NoBuilder {}"); 284 Compilation compilation = daggerCompiler().compile(module, subcomponent); 285 assertThat(compilation).failed(); 286 assertThat(compilation) 287 .hadErrorContaining( 288 "test.NoBuilder doesn't have a @ProductionSubcomponent.Builder or " 289 + "@ProductionSubcomponent.Factory, which is required when used with @" 290 + moduleType.simpleName() 291 + ".subcomponents") 292 .inFile(module) 293 .onLine(5); 294 } 295 296 @Test moduleSubcomponentsAreTypes()297 public void moduleSubcomponentsAreTypes() { 298 JavaFileObject module = 299 JavaFileObjects.forSourceLines( 300 "test.TestModule", 301 "package test;", 302 "", 303 "import dagger.Module;", 304 "", 305 "@Module(subcomponents = int.class)", 306 "class TestModule {}"); 307 Compilation compilation = daggerCompiler().compile(module); 308 assertThat(compilation).failed(); 309 assertThat(compilation) 310 .hadErrorContaining("int is not a valid subcomponent type") 311 .inFile(module) 312 .onLine(5); 313 } 314 315 @Test tooManyAnnotations()316 public void tooManyAnnotations() { 317 assertThatModuleMethod( 318 "@BindsOptionalOf @Multibinds abstract Set<Object> tooManyAnnotations();") 319 .hasError("is annotated with more than one of"); 320 } 321 322 @Test invalidIncludedModule()323 public void invalidIncludedModule() { 324 JavaFileObject badModule = 325 JavaFileObjects.forSourceLines( 326 "test.BadModule", 327 "package test;", 328 "", 329 "import dagger.Binds;", 330 "import dagger.Module;", 331 "", 332 "@Module", 333 "abstract class BadModule {", 334 " @Binds abstract Object noParameters();", 335 "}"); 336 JavaFileObject module = 337 JavaFileObjects.forSourceLines( 338 "test.IncludesBadModule", 339 "package test;", 340 "", 341 "import dagger.Module;", 342 "", 343 "@Module(includes = BadModule.class)", 344 "abstract class IncludesBadModule {}"); 345 Compilation compilation = daggerCompiler().compile(badModule, module); 346 assertThat(compilation).hadErrorCount(2); 347 assertThat(compilation) 348 .hadErrorContaining("test.BadModule has errors") 349 .inFile(module) 350 .onLine(5); 351 assertThat(compilation) 352 .hadErrorContaining( 353 "@Binds methods must have exactly one parameter, whose type is assignable to the " 354 + "return type") 355 .inFile(badModule) 356 .onLine(8); 357 } 358 359 @Test scopeOnModule()360 public void scopeOnModule() { 361 JavaFileObject badModule = 362 JavaFileObjects.forSourceLines( 363 "test.BadModule", 364 "package test;", 365 "", 366 "import dagger.Module;", 367 "import javax.inject.Singleton;", 368 "", 369 "@Singleton", 370 "@Module", 371 "interface BadModule {}"); 372 Compilation compilation = daggerCompiler().compile(badModule); 373 assertThat(compilation).failed(); 374 assertThat(compilation) 375 .hadErrorContaining("@Modules cannot be scoped") 376 .inFile(badModule) 377 .onLineContaining("@Singleton"); 378 } 379 380 @Test moduleIncludesSelfCycle()381 public void moduleIncludesSelfCycle() { 382 JavaFileObject module = 383 JavaFileObjects.forSourceLines( 384 "test.TestModule", 385 "package test;", 386 "", 387 moduleType.importStatement(), 388 "import dagger.Provides;", 389 "", 390 String.format("@%s(", moduleType.simpleName()), 391 " includes = {", 392 " TestModule.class, // first", 393 " OtherModule.class,", 394 " TestModule.class, // second", 395 " }", 396 ")", 397 "class TestModule {", 398 " @Provides int i() { return 0; }", 399 "}"); 400 401 JavaFileObject otherModule = 402 JavaFileObjects.forSourceLines( 403 "test.OtherModule", 404 "package test;", 405 "", 406 "import dagger.Module;", 407 "", 408 "@Module", 409 "class OtherModule {}"); 410 411 Compilation compilation = daggerCompiler().compile(module, otherModule); 412 assertThat(compilation).failed(); 413 String error = String.format("@%s cannot include themselves", moduleType.simpleName()); 414 assertThat(compilation).hadErrorContaining(error).inFile(module).onLineContaining("Module("); 415 } 416 } 417