1 /* 2 * Copyright (C) 2014 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.TestUtils.message; 22 23 import androidx.room.compiler.processing.util.Source; 24 import com.google.common.collect.ImmutableMap; 25 import com.google.testing.compile.Compilation; 26 import com.google.testing.compile.JavaFileObjects; 27 import dagger.testing.compile.CompilerTests; 28 import javax.tools.JavaFileObject; 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.junit.runners.JUnit4; 32 33 @RunWith(JUnit4.class) 34 public final class ComponentValidationTest { 35 @Test componentOnConcreteClass()36 public void componentOnConcreteClass() { 37 Source componentFile = 38 CompilerTests.javaSource( 39 "test.NotAComponent", 40 "package test;", 41 "", 42 "import dagger.Component;", 43 "", 44 "@Component", 45 "final class NotAComponent {}"); 46 CompilerTests.daggerCompiler(componentFile) 47 .compile( 48 subject -> { 49 subject.hasErrorCount(1); 50 subject.hasErrorContaining("interface"); 51 }); 52 } 53 54 @Test componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName()55 public void componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName() { 56 Source componentFile = 57 CompilerTests.javaSource( 58 "test.TestComponent", 59 "package test;", 60 "", 61 "import dagger.Component;", 62 "", 63 "@Component(modules=TestModule.class)", 64 "interface TestComponent {", 65 " String builder();", 66 "}"); 67 Source moduleFile = 68 CompilerTests.javaSource( 69 "test.TestModule", 70 "package test;", 71 "", 72 "import dagger.Module;", 73 "import dagger.Provides;", 74 "", 75 "@Module", 76 "interface TestModule {", 77 " @Provides", 78 " static String provideString() { return \"test\"; }", 79 "}"); 80 81 CompilerTests.daggerCompiler(componentFile, moduleFile) 82 .compile( 83 subject -> { 84 subject.hasErrorCount(1); 85 subject.hasErrorContaining( 86 "The method test.TestComponent.builder() conflicts with a method"); 87 }); 88 } 89 90 @Test componentOnOverridingCreate_failsWhenGeneratedCreateMethod()91 public void componentOnOverridingCreate_failsWhenGeneratedCreateMethod() { 92 Source componentFile = 93 CompilerTests.javaSource( 94 "test.TestComponent", 95 "package test;", 96 "", 97 "import dagger.Component;", 98 "", 99 "@Component(modules=TestModule.class)", 100 "interface TestComponent {", 101 " String create();", 102 "}"); 103 Source moduleFile = 104 CompilerTests.javaSource( 105 "test.TestModule", 106 "package test;", 107 "", 108 "import dagger.Module;", 109 "import dagger.Provides;", 110 "", 111 "@Module", 112 "interface TestModule {", 113 " @Provides", 114 " static String provideString() { return \"test\"; }", 115 "}"); 116 117 CompilerTests.daggerCompiler(componentFile, moduleFile) 118 .compile( 119 subject -> { 120 subject.hasErrorCount(1); 121 subject.hasErrorContaining( 122 "The method test.TestComponent.create() conflicts with a method"); 123 }); 124 } 125 126 @Test subcomponentMethodNameBuilder_succeeds()127 public void subcomponentMethodNameBuilder_succeeds() { 128 Source componentFile = 129 CompilerTests.javaSource( 130 "test.TestComponent", 131 "package test;", 132 "", 133 "import dagger.Component;", 134 "", 135 "@Component", 136 "interface TestComponent {", 137 " TestSubcomponent.Builder subcomponent();", 138 "}"); 139 Source subcomponentFile = 140 CompilerTests.javaSource( 141 "test.TestSubcomponent", 142 "package test;", 143 "", 144 "import dagger.Subcomponent;", 145 "", 146 "@Subcomponent(modules=TestModule.class)", 147 "interface TestSubcomponent {", 148 " String builder();", 149 " @Subcomponent.Builder", 150 " interface Builder {", 151 " TestSubcomponent build();", 152 " }", 153 "}"); 154 Source moduleFile = 155 CompilerTests.javaSource( 156 "test.TestModule", 157 "package test;", 158 "", 159 "import dagger.Module;", 160 "import dagger.Provides;", 161 "", 162 "@Module", 163 "interface TestModule {", 164 " @Provides", 165 " static String provideString() { return \"test\"; }", 166 "}"); 167 168 CompilerTests.daggerCompiler(componentFile, subcomponentFile, moduleFile) 169 .compile(subject -> subject.hasErrorCount(0)); 170 } 171 componentOnEnum()172 @Test public void componentOnEnum() { 173 Source componentFile = 174 CompilerTests.javaSource( 175 "test.NotAComponent", 176 "package test;", 177 "", 178 "import dagger.Component;", 179 "", 180 "@Component", 181 "enum NotAComponent {", 182 " INSTANCE", 183 "}"); 184 CompilerTests.daggerCompiler(componentFile) 185 .compile( 186 subject -> { 187 subject.hasErrorCount(1); 188 subject.hasErrorContaining("interface"); 189 }); 190 } 191 componentOnAnnotation()192 @Test public void componentOnAnnotation() { 193 Source componentFile = 194 CompilerTests.javaSource( 195 "test.NotAComponent", 196 "package test;", 197 "", 198 "import dagger.Component;", 199 "", 200 "@Component", 201 "@interface NotAComponent {}"); 202 CompilerTests.daggerCompiler(componentFile) 203 .compile( 204 subject -> { 205 subject.hasErrorCount(1); 206 subject.hasErrorContaining("interface"); 207 }); 208 } 209 nonModuleModule()210 @Test public void nonModuleModule() { 211 Source componentFile = 212 CompilerTests.javaSource( 213 "test.NotAComponent", 214 "package test;", 215 "", 216 "import dagger.Component;", 217 "", 218 "@Component(modules = Object.class)", 219 "interface NotAComponent {}"); 220 CompilerTests.daggerCompiler(componentFile) 221 .compile( 222 subject -> { 223 subject.hasErrorCount(1); 224 subject.hasErrorContaining("is not annotated with @Module"); 225 }); 226 } 227 228 @Test componentWithInvalidModule()229 public void componentWithInvalidModule() { 230 Source module = 231 CompilerTests.javaSource( 232 "test.BadModule", 233 "package test;", 234 "", 235 "import dagger.Binds;", 236 "import dagger.Module;", 237 "", 238 "@Module", 239 "abstract class BadModule {", 240 " @Binds abstract Object noParameters();", 241 "}"); 242 Source component = 243 CompilerTests.javaSource( 244 "test.BadComponent", 245 "package test;", 246 "", 247 "import dagger.Component;", 248 "", 249 "@Component(modules = BadModule.class)", 250 "interface BadComponent {", 251 " Object object();", 252 "}"); 253 CompilerTests.daggerCompiler(module, component) 254 .compile( 255 subject -> { 256 subject.hasErrorCount(2); 257 subject.hasErrorContaining("test.BadModule has errors") 258 .onSource(component) 259 .onLine(5); 260 subject.hasErrorContaining( 261 "@Binds methods must have exactly one parameter, whose type is assignable to " 262 + "the return type") 263 .onSource(module) 264 .onLine(8); 265 }); 266 } 267 268 @Test attemptToInjectWildcardGenerics()269 public void attemptToInjectWildcardGenerics() { 270 Source testComponent = 271 CompilerTests.javaSource( 272 "test.TestComponent", 273 "package test;", 274 "", 275 "import dagger.Component;", 276 "import dagger.Lazy;", 277 "import javax.inject.Provider;", 278 "", 279 "@Component", 280 "interface TestComponent {", 281 " Lazy<? extends Number> wildcardNumberLazy();", 282 " Provider<? super Number> wildcardNumberProvider();", 283 "}"); 284 CompilerTests.daggerCompiler(testComponent) 285 .compile( 286 subject -> { 287 subject.hasErrorCount(2); 288 subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(9); 289 subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(10); 290 }); 291 } 292 293 // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed. 294 @Test invalidComponentDependencies()295 public void invalidComponentDependencies() { 296 JavaFileObject testComponent = 297 JavaFileObjects.forSourceLines( 298 "test.TestComponent", 299 "package test;", 300 "", 301 "import dagger.Component;", 302 "", 303 "@Component(dependencies = int.class)", 304 "interface TestComponent {}"); 305 Compilation compilation = daggerCompiler().compile(testComponent); 306 assertThat(compilation).failed(); 307 assertThat(compilation).hadErrorContaining("int is not a valid component dependency type"); 308 } 309 310 // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed. 311 @Test invalidComponentModules()312 public void invalidComponentModules() { 313 JavaFileObject testComponent = 314 JavaFileObjects.forSourceLines( 315 "test.TestComponent", 316 "package test;", 317 "", 318 "import dagger.Component;", 319 "", 320 "@Component(modules = int.class)", 321 "interface TestComponent {}"); 322 Compilation compilation = daggerCompiler().compile(testComponent); 323 assertThat(compilation).failed(); 324 assertThat(compilation).hadErrorContaining("int is not a valid module type"); 325 } 326 327 @Test moduleInDependencies()328 public void moduleInDependencies() { 329 Source testModule = 330 CompilerTests.javaSource( 331 "test.TestModule", 332 "package test;", 333 "", 334 "import dagger.Module;", 335 "import dagger.Provides;", 336 "", 337 "@Module", 338 "final class TestModule {", 339 " @Provides String s() { return null; }", 340 "}"); 341 Source testComponent = 342 CompilerTests.javaSource( 343 "test.TestComponent", 344 "package test;", 345 "", 346 "import dagger.Component;", 347 "", 348 "@Component(dependencies = TestModule.class)", 349 "interface TestComponent {}"); 350 CompilerTests.daggerCompiler(testModule, testComponent) 351 .compile( 352 subject -> { 353 subject.hasErrorCount(1); 354 subject.hasErrorContaining( 355 "test.TestModule is a module, which cannot be a component dependency"); 356 }); 357 } 358 359 @Test componentDependencyMustNotCycle_Direct()360 public void componentDependencyMustNotCycle_Direct() { 361 Source shortLifetime = 362 CompilerTests.javaSource( 363 "test.ComponentShort", 364 "package test;", 365 "", 366 "import dagger.Component;", 367 "", 368 "@Component(dependencies = ComponentShort.class)", 369 "interface ComponentShort {", 370 "}"); 371 372 String errorMessage = 373 message( 374 "test.ComponentShort contains a cycle in its component dependencies:", 375 " test.ComponentShort"); 376 CompilerTests.daggerCompiler(shortLifetime) 377 .compile( 378 subject -> { 379 subject.hasErrorCount(1); 380 subject.hasErrorContaining(errorMessage); 381 }); 382 383 // Test that this also fails when transitive validation is disabled. 384 CompilerTests.daggerCompiler(shortLifetime) 385 .withProcessingOptions( 386 ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED")) 387 .compile( 388 subject -> { 389 subject.hasErrorCount(1); 390 subject.hasErrorContaining(errorMessage); 391 }); 392 } 393 394 @Test componentDependencyMustNotCycle_Indirect()395 public void componentDependencyMustNotCycle_Indirect() { 396 Source longLifetime = 397 CompilerTests.javaSource( 398 "test.ComponentLong", 399 "package test;", 400 "", 401 "import dagger.Component;", 402 "", 403 "@Component(dependencies = ComponentMedium.class)", 404 "interface ComponentLong {", 405 "}"); 406 Source mediumLifetime = 407 CompilerTests.javaSource( 408 "test.ComponentMedium", 409 "package test;", 410 "", 411 "import dagger.Component;", 412 "", 413 "@Component(dependencies = ComponentLong.class)", 414 "interface ComponentMedium {", 415 "}"); 416 Source shortLifetime = 417 CompilerTests.javaSource( 418 "test.ComponentShort", 419 "package test;", 420 "", 421 "import dagger.Component;", 422 "", 423 "@Component(dependencies = ComponentMedium.class)", 424 "interface ComponentShort {", 425 "}"); 426 427 CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime) 428 .compile( 429 subject -> { 430 subject.hasErrorCount(3); 431 subject.hasErrorContaining( 432 message( 433 "test.ComponentLong contains a cycle in its component dependencies:", 434 " test.ComponentLong", 435 " test.ComponentMedium", 436 " test.ComponentLong")) 437 .onSource(longLifetime); 438 subject.hasErrorContaining( 439 message( 440 "test.ComponentMedium contains a cycle in its component dependencies:", 441 " test.ComponentMedium", 442 " test.ComponentLong", 443 " test.ComponentMedium")) 444 .onSource(mediumLifetime); 445 subject.hasErrorContaining( 446 message( 447 "test.ComponentShort contains a cycle in its component dependencies:", 448 " test.ComponentMedium", 449 " test.ComponentLong", 450 " test.ComponentMedium", 451 " test.ComponentShort")) 452 .onSource(shortLifetime); 453 }); 454 455 // Test that compilation succeeds when transitive validation is disabled because the cycle 456 // cannot be detected. 457 CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime) 458 .withProcessingOptions( 459 ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED")) 460 .compile(subject -> subject.hasErrorCount(0)); 461 } 462 463 @Test abstractModuleWithInstanceMethod()464 public void abstractModuleWithInstanceMethod() { 465 Source module = 466 CompilerTests.javaSource( 467 "test.TestModule", 468 "package test;", 469 "", 470 "import dagger.Module;", 471 "import dagger.Provides;", 472 "", 473 "@Module", 474 "abstract class TestModule {", 475 " @Provides int i() { return 1; }", 476 "}"); 477 Source component = 478 CompilerTests.javaSource( 479 "test.TestComponent", 480 "package test;", 481 "", 482 "import dagger.Component;", 483 "", 484 "@Component(modules = TestModule.class)", 485 "interface TestComponent {", 486 " int i();", 487 "}"); 488 CompilerTests.daggerCompiler(module, component) 489 .compile( 490 subject -> { 491 subject.hasErrorCount(1); 492 subject.hasErrorContaining( 493 "TestModule is abstract and has instance @Provides methods") 494 .onSource(component) 495 .onLineContaining("interface TestComponent"); 496 }); 497 } 498 499 @Test abstractModuleWithInstanceMethod_subclassedIsAllowed()500 public void abstractModuleWithInstanceMethod_subclassedIsAllowed() { 501 Source abstractModule = 502 CompilerTests.javaSource( 503 "test.AbstractModule", 504 "package test;", 505 "", 506 "import dagger.Module;", 507 "import dagger.Provides;", 508 "", 509 "@Module", 510 "abstract class AbstractModule {", 511 " @Provides int i() { return 1; }", 512 "}"); 513 Source subclassedModule = 514 CompilerTests.javaSource( 515 "test.SubclassedModule", 516 "package test;", 517 "", 518 "import dagger.Module;", 519 "", 520 "@Module", 521 "class SubclassedModule extends AbstractModule {}"); 522 Source component = 523 CompilerTests.javaSource( 524 "test.TestComponent", 525 "package test;", 526 "", 527 "import dagger.Component;", 528 "", 529 "@Component(modules = SubclassedModule.class)", 530 "interface TestComponent {", 531 " int i();", 532 "}"); 533 CompilerTests.daggerCompiler(abstractModule, subclassedModule, component) 534 .compile(subject -> subject.hasErrorCount(0)); 535 } 536 } 537