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 dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod; 20 21 import androidx.room.compiler.processing.XProcessingEnv; 22 import androidx.room.compiler.processing.util.Source; 23 import dagger.Module; 24 import dagger.producers.ProducerModule; 25 import dagger.testing.compile.CompilerTests; 26 import java.lang.annotation.Annotation; 27 import java.util.Arrays; 28 import java.util.Collection; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.junit.runners.Parameterized; 32 33 @RunWith(Parameterized.class) 34 public final class ModuleValidationTest { 35 36 @Parameterized.Parameters(name = "{0}") parameters()37 public static Collection<Object[]> parameters() { 38 return Arrays.asList(new Object[][] {{ModuleType.MODULE}, {ModuleType.PRODUCER_MODULE}}); 39 } 40 41 private enum ModuleType { 42 MODULE(Module.class), 43 PRODUCER_MODULE(ProducerModule.class), 44 ; 45 46 private final Class<? extends Annotation> annotation; 47 ModuleType(Class<? extends Annotation> annotation)48 ModuleType(Class<? extends Annotation> annotation) { 49 this.annotation = annotation; 50 } 51 annotationWithSubcomponent(String subcomponent)52 String annotationWithSubcomponent(String subcomponent) { 53 return String.format("@%s(subcomponents = %s)", annotation.getSimpleName(), subcomponent); 54 } 55 importStatement()56 String importStatement() { 57 return String.format("import %s;", annotation.getName()); 58 } 59 simpleName()60 String simpleName() { 61 return annotation.getSimpleName(); 62 } 63 } 64 65 private final ModuleType moduleType; 66 ModuleValidationTest(ModuleType moduleType)67 public ModuleValidationTest(ModuleType moduleType) { 68 this.moduleType = moduleType; 69 } 70 71 @Test moduleSubcomponents_notASubcomponent()72 public void moduleSubcomponents_notASubcomponent() { 73 Source module = 74 CompilerTests.javaSource( 75 "test.TestModule", 76 "package test;", 77 "", 78 moduleType.importStatement(), 79 "", 80 moduleType.annotationWithSubcomponent("NotASubcomponent.class"), 81 "class TestModule {}"); 82 Source notASubcomponent = 83 CompilerTests.javaSource( 84 "test.NotASubcomponent", 85 "package test;", 86 "", 87 "class NotASubcomponent {}"); 88 CompilerTests.daggerCompiler(module, notASubcomponent) 89 .compile( 90 subject -> { 91 subject.hasErrorCount(1); 92 subject.hasErrorContaining( 93 "test.NotASubcomponent is not a @Subcomponent or @ProductionSubcomponent") 94 .onSource(module) 95 .onLine(5); 96 }); 97 } 98 99 @Test moduleSubcomponents_listsSubcomponentBuilder()100 public void moduleSubcomponents_listsSubcomponentBuilder() { 101 Source module = 102 CompilerTests.javaSource( 103 "test.TestModule", 104 "package test;", 105 "", 106 moduleType.importStatement(), 107 "", 108 moduleType.annotationWithSubcomponent("Sub.Builder.class"), 109 "class TestModule {}"); 110 Source subcomponent = 111 CompilerTests.javaSource( 112 "test.Sub", 113 "package test;", 114 "", 115 "import dagger.Subcomponent;", 116 "", 117 "@Subcomponent", 118 "interface Sub {", 119 " @Subcomponent.Builder", 120 " interface Builder {", 121 " Sub build();", 122 " }", 123 "}"); 124 CompilerTests.daggerCompiler(module, subcomponent) 125 .compile( 126 subject -> { 127 subject.hasErrorCount(1); 128 subject.hasErrorContaining( 129 "test.Sub.Builder is a @Subcomponent.Builder. Did you mean to use test.Sub?") 130 .onSource(module) 131 .onLine(5); 132 }); 133 } 134 135 @Test moduleSubcomponents_listsSubcomponentFactory()136 public void moduleSubcomponents_listsSubcomponentFactory() { 137 Source module = 138 CompilerTests.javaSource( 139 "test.TestModule", 140 "package test;", 141 "", 142 moduleType.importStatement(), 143 "", 144 moduleType.annotationWithSubcomponent("Sub.Factory.class"), 145 "class TestModule {}"); 146 Source subcomponent = 147 CompilerTests.javaSource( 148 "test.Sub", 149 "package test;", 150 "", 151 "import dagger.Subcomponent;", 152 "", 153 "@Subcomponent", 154 "interface Sub {", 155 " @Subcomponent.Factory", 156 " interface Factory {", 157 " Sub creator();", 158 " }", 159 "}"); 160 CompilerTests.daggerCompiler(module, subcomponent) 161 .compile( 162 subject -> { 163 subject.hasErrorCount(1); 164 subject.hasErrorContaining( 165 "test.Sub.Factory is a @Subcomponent.Factory. Did you mean to use test.Sub?") 166 .onSource(module) 167 .onLine(5); 168 }); 169 } 170 171 @Test moduleSubcomponents_listsProductionSubcomponentBuilder()172 public void moduleSubcomponents_listsProductionSubcomponentBuilder() { 173 Source module = 174 CompilerTests.javaSource( 175 "test.TestModule", 176 "package test;", 177 "", 178 moduleType.importStatement(), 179 "", 180 moduleType.annotationWithSubcomponent("Sub.Builder.class"), 181 "class TestModule {}"); 182 Source subcomponent = 183 CompilerTests.javaSource( 184 "test.Sub", 185 "package test;", 186 "", 187 "import dagger.producers.ProductionSubcomponent;", 188 "", 189 "@ProductionSubcomponent", 190 "interface Sub {", 191 " @ProductionSubcomponent.Builder", 192 " interface Builder {", 193 " Sub build();", 194 " }", 195 "}"); 196 CompilerTests.daggerCompiler(module, subcomponent) 197 .compile( 198 subject -> { 199 subject.hasErrorCount(1); 200 subject.hasErrorContaining( 201 "test.Sub.Builder is a @ProductionSubcomponent.Builder. " 202 + "Did you mean to use test.Sub?") 203 .onSource(module) 204 .onLine(5); 205 }); 206 } 207 208 @Test moduleSubcomponents_listsProductionSubcomponentFactory()209 public void moduleSubcomponents_listsProductionSubcomponentFactory() { 210 Source module = 211 CompilerTests.javaSource( 212 "test.TestModule", 213 "package test;", 214 "", 215 moduleType.importStatement(), 216 "", 217 moduleType.annotationWithSubcomponent("Sub.Factory.class"), 218 "class TestModule {}"); 219 Source subcomponent = 220 CompilerTests.javaSource( 221 "test.Sub", 222 "package test;", 223 "", 224 "import dagger.producers.ProductionSubcomponent;", 225 "", 226 "@ProductionSubcomponent", 227 "interface Sub {", 228 " @ProductionSubcomponent.Factory", 229 " interface Factory {", 230 " Sub create();", 231 " }", 232 "}"); 233 CompilerTests.daggerCompiler(module, subcomponent) 234 .compile( 235 subject -> { 236 subject.hasErrorCount(1); 237 subject.hasErrorContaining( 238 "test.Sub.Factory is a @ProductionSubcomponent.Factory. " 239 + "Did you mean to use test.Sub?") 240 .onSource(module) 241 .onLine(5); 242 }); 243 } 244 245 @Test moduleSubcomponents_noSubcomponentCreator()246 public void moduleSubcomponents_noSubcomponentCreator() { 247 Source module = 248 CompilerTests.javaSource( 249 "test.TestModule", 250 "package test;", 251 "", 252 moduleType.importStatement(), 253 "", 254 moduleType.annotationWithSubcomponent("NoBuilder.class"), 255 "class TestModule {}"); 256 Source subcomponent = 257 CompilerTests.javaSource( 258 "test.NoBuilder", 259 "package test;", 260 "", 261 "import dagger.Subcomponent;", 262 "", 263 "@Subcomponent", 264 "interface NoBuilder {}"); 265 CompilerTests.daggerCompiler(module, subcomponent) 266 .compile( 267 subject -> { 268 subject.hasErrorCount(1); 269 subject.hasErrorContaining( 270 "test.NoBuilder doesn't have a @Subcomponent.Builder or " 271 + "@Subcomponent.Factory, which is required when used with @" 272 + moduleType.simpleName() 273 + ".subcomponents") 274 .onSource(module) 275 .onLine(5); 276 }); 277 } 278 279 @Test moduleSubcomponents_noProductionSubcomponentCreator()280 public void moduleSubcomponents_noProductionSubcomponentCreator() { 281 Source module = 282 CompilerTests.javaSource( 283 "test.TestModule", 284 "package test;", 285 "", 286 moduleType.importStatement(), 287 "", 288 moduleType.annotationWithSubcomponent("NoBuilder.class"), 289 "class TestModule {}"); 290 Source subcomponent = 291 CompilerTests.javaSource( 292 "test.NoBuilder", 293 "package test;", 294 "", 295 "import dagger.producers.ProductionSubcomponent;", 296 "", 297 "@ProductionSubcomponent", 298 "interface NoBuilder {}"); 299 CompilerTests.daggerCompiler(module, subcomponent) 300 .compile( 301 subject -> { 302 subject.hasErrorCount(1); 303 subject.hasErrorContaining( 304 "test.NoBuilder doesn't have a @ProductionSubcomponent.Builder or " 305 + "@ProductionSubcomponent.Factory, which is required when used with @" 306 + moduleType.simpleName() 307 + ".subcomponents") 308 .onSource(module) 309 .onLine(5); 310 }); 311 } 312 313 @Test moduleSubcomponentsAreTypes()314 public void moduleSubcomponentsAreTypes() { 315 Source module = 316 CompilerTests.javaSource( 317 "test.TestModule", 318 "package test;", 319 "", 320 "import dagger.Module;", 321 "", 322 "@Module(subcomponents = int.class)", 323 "class TestModule {}"); 324 CompilerTests.daggerCompiler(module) 325 .compile( 326 subject -> { 327 subject.hasErrorCount(1); 328 switch (CompilerTests.backend(subject)) { 329 case JAVAC: 330 subject.hasErrorContaining("int is not a valid subcomponent type") 331 .onSource(module) 332 .onLine(5); 333 break; 334 case KSP: 335 // TODO(b/245954367): Remove this pathway once this bug is fixed. 336 // KSP interprets the int.class type as a boxed type so we get a slightly 337 // different error message for now. 338 subject.hasErrorContaining( 339 "java.lang.Integer is not a @Subcomponent or @ProductionSubcomponent") 340 .onSource(module) 341 .onLine(5); 342 break; 343 } 344 }); 345 } 346 347 @Test tooManyAnnotations()348 public void tooManyAnnotations() { 349 assertThatModuleMethod( 350 "@BindsOptionalOf @Multibinds abstract Set<Object> tooManyAnnotations();") 351 .hasError("is annotated with more than one of"); 352 } 353 354 @Test invalidIncludedModule()355 public void invalidIncludedModule() { 356 Source badModule = 357 CompilerTests.javaSource( 358 "test.BadModule", 359 "package test;", 360 "", 361 "import dagger.Binds;", 362 "import dagger.Module;", 363 "", 364 "@Module", 365 "abstract class BadModule {", 366 " @Binds abstract Object noParameters();", 367 "}"); 368 Source module = 369 CompilerTests.javaSource( 370 "test.IncludesBadModule", 371 "package test;", 372 "", 373 "import dagger.Module;", 374 "", 375 "@Module(includes = BadModule.class)", 376 "abstract class IncludesBadModule {}"); 377 CompilerTests.daggerCompiler(badModule, module) 378 .compile( 379 subject -> { 380 subject.hasErrorCount(2); 381 subject.hasErrorContaining("test.BadModule has errors") 382 .onSource(module) 383 .onLine(5); 384 subject.hasErrorContaining( 385 "@Binds methods must have exactly one parameter, whose type is " 386 + "assignable to the return type") 387 .onSource(badModule) 388 .onLine(8); 389 }); 390 } 391 392 @Test scopeOnModule()393 public void scopeOnModule() { 394 Source badModule = 395 CompilerTests.javaSource( 396 "test.BadModule", 397 "package test;", 398 "", 399 "import dagger.Module;", 400 "import javax.inject.Singleton;", 401 "", 402 "@Singleton", 403 "@Module", 404 "interface BadModule {}"); 405 CompilerTests.daggerCompiler(badModule) 406 .compile( 407 subject -> { 408 subject.hasErrorCount(1); 409 subject.hasErrorContaining("@Modules cannot be scoped") 410 .onSource(badModule) 411 .onLineContaining("@Singleton"); 412 }); 413 } 414 415 @Test moduleIncludesSelfCycle()416 public void moduleIncludesSelfCycle() { 417 Source module = 418 CompilerTests.javaSource( 419 "test.TestModule", 420 "package test;", 421 "", 422 moduleType.importStatement(), 423 "import dagger.Provides;", 424 "", 425 String.format("@%s(", moduleType.simpleName()), 426 " includes = {", 427 " TestModule.class, // first", 428 " OtherModule.class,", 429 " TestModule.class, // second", 430 " }", 431 ")", 432 "class TestModule {", 433 " @Provides int i() { return 0; }", 434 "}"); 435 436 Source otherModule = 437 CompilerTests.javaSource( 438 "test.OtherModule", 439 "package test;", 440 "", 441 "import dagger.Module;", 442 "", 443 "@Module", 444 "class OtherModule {}"); 445 446 CompilerTests.daggerCompiler(module, otherModule) 447 .compile( 448 subject -> { 449 subject.hasErrorCount(2); 450 String error = 451 String.format("@%s cannot include themselves", moduleType.simpleName()); 452 switch (CompilerTests.backend(subject)) { 453 case JAVAC: 454 subject.hasErrorContaining(error).onSource(module).onLineContaining("// first"); 455 subject.hasErrorContaining(error).onSource(module).onLineContaining("// second"); 456 break; 457 case KSP: 458 // KSP doesn't support reporting errors on individual annotation values, so both 459 // errors will be reported on the annotation itself. 460 subject.hasErrorContaining(error) 461 .onSource(module) 462 .onLineContaining("@" + moduleType.simpleName()); 463 break; 464 } 465 }); 466 } 467 468 // Regression test for b/264618194. 469 @Test objectModuleInheritsInstanceBindingFails()470 public void objectModuleInheritsInstanceBindingFails() { 471 Source objectModule = 472 CompilerTests.kotlinSource( 473 "test.ObjectModule.kt", 474 "package test", 475 "", 476 "import dagger.Module", 477 "import dagger.Provides", 478 "", 479 "@Module", 480 "object ObjectModule : ClassModule() {", 481 " @Provides fun provideString(): String = \"\"", 482 "}"); 483 Source classModule = 484 CompilerTests.kotlinSource( 485 "test.ClassModule.kt", 486 "package test", 487 "", 488 "import dagger.Module", 489 "import dagger.Provides", 490 "", 491 "@Module", 492 "abstract class ClassModule {", 493 " @Provides fun provideInt(): Int = 1", 494 "}"); 495 Source component = 496 CompilerTests.kotlinSource( 497 "test.TestComponent.kt", 498 "package test", 499 "", 500 "import dagger.Component", 501 "", 502 "@Component(modules = [ObjectModule::class])", 503 "interface TestComponent {", 504 " fun getInt(): Int", 505 " fun getString(): String", 506 "}"); 507 508 CompilerTests.daggerCompiler(component, objectModule, classModule) 509 .compile( 510 subject -> { 511 subject.hasErrorCount(2); 512 subject.hasErrorContaining("test.ObjectModule has errors") 513 .onSource(component) 514 .onLineContaining("ObjectModule::class"); 515 subject.hasErrorContaining( 516 "@Module-annotated Kotlin object cannot inherit instance " 517 + "(i.e. non-abstract, non-JVM static) binding method: " 518 + "@Provides int test.ClassModule.provideInt()") 519 .onSource(objectModule) 520 .onLineContaining( 521 // TODO(b/267223703): KAPT incorrectly reports the error on the annotation. 522 CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC 523 ? "@Module" 524 : "object ObjectModule"); 525 }); 526 } 527 528 // Regression test for b/264618194. 529 @Test objectModuleInheritsNonInstanceBindingSucceeds()530 public void objectModuleInheritsNonInstanceBindingSucceeds() { 531 Source objectModule = 532 CompilerTests.kotlinSource( 533 "test.ObjectModule.kt", 534 "package test", 535 "", 536 "import dagger.Module", 537 "import dagger.Provides", 538 "", 539 "@Module", 540 "object ObjectModule : ClassModule() {", 541 " @Provides fun provideString(): String = \"\"", 542 "}"); 543 Source classModule = 544 CompilerTests.javaSource( 545 "test.ClassModule", 546 "package test;", 547 "", 548 "import dagger.Binds;", 549 "import dagger.Module;", 550 "import dagger.Provides;", 551 "", 552 "@Module", 553 "public abstract class ClassModule {", 554 " // A non-binding instance method is okay.", 555 " public int nonBindingMethod() {", 556 " return 1;", 557 " }", 558 "", 559 " // A static binding method is also okay.", 560 " @Provides", 561 " public static int provideInt() {", 562 " return 1;", 563 " }", 564 "}"); 565 Source component = 566 CompilerTests.kotlinSource( 567 "test.TestComponent.kt", 568 "package test", 569 "", 570 "import dagger.Component", 571 "", 572 "@Component(modules = [ObjectModule::class])", 573 "interface TestComponent {", 574 " fun getInt(): Int", 575 " fun getString(): String", 576 "}"); 577 CompilerTests.daggerCompiler(component, objectModule, classModule) 578 .compile(subject -> subject.hasErrorCount(0)); 579 } 580 } 581