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.compilerWithOptions; 21 import static dagger.internal.codegen.Compilers.daggerCompiler; 22 import static dagger.internal.codegen.TestUtils.message; 23 24 import com.google.testing.compile.Compilation; 25 import com.google.testing.compile.JavaFileObjects; 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 ScopingValidationTest { 33 @Test componentWithoutScopeIncludesScopedBindings_Fail()34 public void componentWithoutScopeIncludesScopedBindings_Fail() { 35 JavaFileObject componentFile = 36 JavaFileObjects.forSourceLines( 37 "test.MyComponent", 38 "package test;", 39 "", 40 "import dagger.Component;", 41 "import javax.inject.Singleton;", 42 "", 43 "@Component(modules = ScopedModule.class)", 44 "interface MyComponent {", 45 " ScopedType string();", 46 "}"); 47 JavaFileObject typeFile = 48 JavaFileObjects.forSourceLines( 49 "test.ScopedType", 50 "package test;", 51 "", 52 "import javax.inject.Inject;", 53 "import javax.inject.Singleton;", 54 "", 55 "@Singleton", 56 "class ScopedType {", 57 " @Inject ScopedType(String s, long l, float f) {}", 58 "}"); 59 JavaFileObject moduleFile = 60 JavaFileObjects.forSourceLines( 61 "test.ScopedModule", 62 "package test;", 63 "", 64 "import dagger.Module;", 65 "import dagger.Provides;", 66 "import javax.inject.Singleton;", 67 "", 68 "@Module", 69 "class ScopedModule {", 70 " @Provides @Singleton String string() { return \"a string\"; }", 71 " @Provides long integer() { return 0L; }", 72 " @Provides float floatingPoint() { return 0.0f; }", 73 "}"); 74 75 Compilation compilation = daggerCompiler().compile(componentFile, typeFile, moduleFile); 76 assertThat(compilation).failed(); 77 assertThat(compilation) 78 .hadErrorContaining( 79 message( 80 "MyComponent (unscoped) may not reference scoped bindings:", 81 " @Singleton class ScopedType", 82 " @Provides @Singleton String ScopedModule.string()")); 83 } 84 85 @Test // b/79859714 bindsWithChildScope_inParentModule_notAllowed()86 public void bindsWithChildScope_inParentModule_notAllowed() { 87 JavaFileObject childScope = 88 JavaFileObjects.forSourceLines( 89 "test.ChildScope", 90 "package test;", 91 "", 92 "import javax.inject.Scope;", 93 "", 94 "@Scope", 95 "@interface ChildScope {}"); 96 97 JavaFileObject foo = 98 JavaFileObjects.forSourceLines( 99 "test.Foo", 100 "package test;", 101 "", // 102 "interface Foo {}"); 103 104 JavaFileObject fooImpl = 105 JavaFileObjects.forSourceLines( 106 "test.ChildModule", 107 "package test;", 108 "", 109 "import javax.inject.Inject;", 110 "", 111 "class FooImpl implements Foo {", 112 " @Inject FooImpl() {}", 113 "}"); 114 115 JavaFileObject parentModule = 116 JavaFileObjects.forSourceLines( 117 "test.ParentModule", 118 "package test;", 119 "", 120 "import dagger.Binds;", 121 "import dagger.Module;", 122 "", 123 "@Module", 124 "interface ParentModule {", 125 " @Binds @ChildScope Foo bind(FooImpl fooImpl);", 126 "}"); 127 128 JavaFileObject parent = 129 JavaFileObjects.forSourceLines( 130 "test.ParentComponent", 131 "package test;", 132 "", 133 "import dagger.Component;", 134 "import javax.inject.Singleton;", 135 "", 136 "@Singleton", 137 "@Component(modules = ParentModule.class)", 138 "interface Parent {", 139 " Child child();", 140 "}"); 141 142 JavaFileObject child = 143 JavaFileObjects.forSourceLines( 144 "test.Child", 145 "package test;", 146 "", 147 "import dagger.Subcomponent;", 148 "", 149 "@ChildScope", 150 "@Subcomponent", 151 "interface Child {", 152 " Foo foo();", 153 "}"); 154 155 Compilation compilation = 156 daggerCompiler().compile(childScope, foo, fooImpl, parentModule, parent, child); 157 assertThat(compilation).failed(); 158 assertThat(compilation) 159 .hadErrorContaining( 160 message( 161 "Parent scoped with @Singleton may not reference bindings with different " 162 + "scopes:", 163 " @Binds @ChildScope Foo ParentModule.bind(FooImpl)")); 164 } 165 166 @Test componentWithScopeIncludesIncompatiblyScopedBindings_Fail()167 public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() { 168 JavaFileObject componentFile = 169 JavaFileObjects.forSourceLines( 170 "test.MyComponent", 171 "package test;", 172 "", 173 "import dagger.Component;", 174 "import javax.inject.Singleton;", 175 "", 176 "@Singleton", 177 "@Component(modules = ScopedModule.class)", 178 "interface MyComponent {", 179 " ScopedType string();", 180 "}"); 181 JavaFileObject scopeFile = 182 JavaFileObjects.forSourceLines( 183 "test.PerTest", 184 "package test;", 185 "", 186 "import javax.inject.Scope;", 187 "", 188 "@Scope", 189 "@interface PerTest {}"); 190 JavaFileObject scopeWithAttribute = 191 JavaFileObjects.forSourceLines( 192 "test.Per", 193 "package test;", 194 "", 195 "import javax.inject.Scope;", 196 "", 197 "@Scope", 198 "@interface Per {", 199 " Class<?> value();", 200 "}"); 201 JavaFileObject typeFile = 202 JavaFileObjects.forSourceLines( 203 "test.ScopedType", 204 "package test;", 205 "", 206 "import javax.inject.Inject;", 207 "", 208 "@PerTest", // incompatible scope 209 "class ScopedType {", 210 " @Inject ScopedType(String s, long l, float f, boolean b) {}", 211 "}"); 212 JavaFileObject moduleFile = 213 JavaFileObjects.forSourceLines( 214 "test.ScopedModule", 215 "package test;", 216 "", 217 "import dagger.Module;", 218 "import dagger.Provides;", 219 "import javax.inject.Singleton;", 220 "", 221 "@Module", 222 "class ScopedModule {", 223 " @Provides @PerTest String string() { return \"a string\"; }", // incompatible scope 224 " @Provides long integer() { return 0L; }", // unscoped - valid 225 " @Provides @Singleton float floatingPoint() { return 0.0f; }", // same scope - valid 226 " @Provides @Per(MyComponent.class) boolean bool() { return false; }", // incompatible 227 "}"); 228 229 Compilation compilation = 230 daggerCompiler() 231 .compile(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile); 232 assertThat(compilation).failed(); 233 assertThat(compilation) 234 .hadErrorContaining( 235 message( 236 "MyComponent scoped with @Singleton " 237 + "may not reference bindings with different scopes:", 238 " @PerTest class ScopedType", 239 " @Provides @PerTest String ScopedModule.string()", 240 " @Provides @Per(MyComponent.class) boolean " 241 + "ScopedModule.bool()")) 242 .inFile(componentFile) 243 .onLineContaining("interface MyComponent"); 244 245 compilation = 246 compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") 247 .compile(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile); 248 // The @Inject binding for ScopedType should not appear here, but the @Singleton binding should. 249 assertThat(compilation) 250 .hadErrorContaining( 251 message( 252 "ScopedModule contains bindings with different scopes:", 253 " @Provides @PerTest String ScopedModule.string()", 254 " @Provides @Singleton float ScopedModule.floatingPoint()", 255 " @Provides @Per(MyComponent.class) boolean " 256 + "ScopedModule.bool()")) 257 .inFile(moduleFile) 258 .onLineContaining("class ScopedModule"); 259 } 260 261 @Test fullBindingGraphValidationDoesNotReportForOneScope()262 public void fullBindingGraphValidationDoesNotReportForOneScope() { 263 Compilation compilation = 264 compilerWithOptions( 265 "-Adagger.fullBindingGraphValidation=ERROR", 266 "-Adagger.moduleHasDifferentScopesValidation=ERROR") 267 .compile( 268 JavaFileObjects.forSourceLines( 269 "test.TestModule", 270 "package test;", 271 "", 272 "import dagger.Module;", 273 "import dagger.Provides;", 274 "import javax.inject.Singleton;", 275 "", 276 "@Module", 277 "interface TestModule {", 278 " @Provides @Singleton static Object object() { return \"object\"; }", 279 " @Provides @Singleton static String string() { return \"string\"; }", 280 " @Provides static int integer() { return 4; }", 281 "}")); 282 assertThat(compilation).succeededWithoutWarnings(); 283 } 284 285 @Test fullBindingGraphValidationDoesNotReportInjectBindings()286 public void fullBindingGraphValidationDoesNotReportInjectBindings() { 287 Compilation compilation = 288 compilerWithOptions( 289 "-Adagger.fullBindingGraphValidation=ERROR", 290 "-Adagger.moduleHasDifferentScopesValidation=ERROR") 291 .compile( 292 JavaFileObjects.forSourceLines( 293 "test.UsedInRootRedScoped", 294 "package test;", 295 "", 296 "import javax.inject.Inject;", 297 "", 298 "@RedScope", 299 "final class UsedInRootRedScoped {", 300 " @Inject UsedInRootRedScoped() {}", 301 "}"), 302 JavaFileObjects.forSourceLines( 303 "test.UsedInRootBlueScoped", 304 "package test;", 305 "", 306 "import javax.inject.Inject;", 307 "", 308 "@BlueScope", 309 "final class UsedInRootBlueScoped {", 310 " @Inject UsedInRootBlueScoped() {}", 311 "}"), 312 JavaFileObjects.forSourceLines( 313 "test.RedScope", 314 "package test;", 315 "", 316 "import javax.inject.Scope;", 317 "", 318 "@Scope", 319 "@interface RedScope {}"), 320 JavaFileObjects.forSourceLines( 321 "test.BlueScope", 322 "package test;", 323 "", 324 "import javax.inject.Scope;", 325 "", 326 "@Scope", 327 "@interface BlueScope {}"), 328 JavaFileObjects.forSourceLines( 329 "test.TestModule", 330 "package test;", 331 "", 332 "import dagger.Module;", 333 "import dagger.Provides;", 334 "import javax.inject.Singleton;", 335 "", 336 "@Module(subcomponents = Child.class)", 337 "interface TestModule {", 338 " @Provides @Singleton", 339 " static Object object(", 340 " UsedInRootRedScoped usedInRootRedScoped,", 341 " UsedInRootBlueScoped usedInRootBlueScoped) {", 342 " return \"object\";", 343 " }", 344 "}"), 345 JavaFileObjects.forSourceLines( 346 "test.Child", 347 "package test;", 348 "", 349 "import dagger.Subcomponent;", 350 "", 351 "@Subcomponent", 352 "interface Child {", 353 " UsedInChildRedScoped usedInChildRedScoped();", 354 " UsedInChildBlueScoped usedInChildBlueScoped();", 355 "", 356 " @Subcomponent.Builder", 357 " interface Builder {", 358 " Child child();", 359 " }", 360 "}"), 361 JavaFileObjects.forSourceLines( 362 "test.UsedInChildRedScoped", 363 "package test;", 364 "", 365 "import javax.inject.Inject;", 366 "", 367 "@RedScope", 368 "final class UsedInChildRedScoped {", 369 " @Inject UsedInChildRedScoped() {}", 370 "}"), 371 JavaFileObjects.forSourceLines( 372 "test.UsedInChildBlueScoped", 373 "package test;", 374 "", 375 "import javax.inject.Inject;", 376 "", 377 "@BlueScope", 378 "final class UsedInChildBlueScoped {", 379 " @Inject UsedInChildBlueScoped() {}", 380 "}")); 381 assertThat(compilation).succeededWithoutWarnings(); 382 } 383 384 @Test componentWithScopeCanDependOnMultipleScopedComponents()385 public void componentWithScopeCanDependOnMultipleScopedComponents() { 386 // If a scoped component will have dependencies, they can include multiple scoped component 387 JavaFileObject type = 388 JavaFileObjects.forSourceLines( 389 "test.SimpleType", 390 "package test;", 391 "", 392 "import javax.inject.Inject;", 393 "", 394 "class SimpleType {", 395 " @Inject SimpleType() {}", 396 " static class A { @Inject A() {} }", 397 " static class B { @Inject B() {} }", 398 "}"); 399 JavaFileObject simpleScope = 400 JavaFileObjects.forSourceLines( 401 "test.SimpleScope", 402 "package test;", 403 "", 404 "import javax.inject.Scope;", 405 "", 406 "@Scope @interface SimpleScope {}"); 407 JavaFileObject singletonScopedA = 408 JavaFileObjects.forSourceLines( 409 "test.SingletonComponentA", 410 "package test;", 411 "", 412 "import dagger.Component;", 413 "import javax.inject.Singleton;", 414 "", 415 "@Singleton", 416 "@Component", 417 "interface SingletonComponentA {", 418 " SimpleType.A type();", 419 "}"); 420 JavaFileObject singletonScopedB = 421 JavaFileObjects.forSourceLines( 422 "test.SingletonComponentB", 423 "package test;", 424 "", 425 "import dagger.Component;", 426 "import javax.inject.Singleton;", 427 "", 428 "@Singleton", 429 "@Component", 430 "interface SingletonComponentB {", 431 " SimpleType.B type();", 432 "}"); 433 JavaFileObject scopeless = 434 JavaFileObjects.forSourceLines( 435 "test.ScopelessComponent", 436 "package test;", 437 "", 438 "import dagger.Component;", 439 "", 440 "@Component", 441 "interface ScopelessComponent {", 442 " SimpleType type();", 443 "}"); 444 JavaFileObject simpleScoped = 445 JavaFileObjects.forSourceLines( 446 "test.SimpleScopedComponent", 447 "package test;", 448 "", 449 "import dagger.Component;", 450 "", 451 "@SimpleScope", 452 "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})", 453 "interface SimpleScopedComponent {", 454 " SimpleType.A type();", 455 "}"); 456 457 Compilation compilation = 458 daggerCompiler() 459 .compile( 460 type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless); 461 assertThat(compilation).succeededWithoutWarnings(); 462 } 463 464 465 466 // Tests the following component hierarchy: 467 // 468 // @ScopeA 469 // ComponentA 470 // [SimpleType getSimpleType()] 471 // / \ 472 // / \ 473 // @ScopeB @ScopeB 474 // ComponentB1 ComponentB2 475 // \ [SimpleType getSimpleType()] 476 // \ / 477 // \ / 478 // @ScopeC 479 // ComponentC 480 // [SimpleType getSimpleType()] 481 @Test componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond()482 public void componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond() { 483 JavaFileObject type = 484 JavaFileObjects.forSourceLines( 485 "test.SimpleType", 486 "package test;", 487 "", 488 "import javax.inject.Inject;", 489 "", 490 "class SimpleType {", 491 " @Inject SimpleType() {}", 492 "}"); 493 JavaFileObject simpleScope = 494 JavaFileObjects.forSourceLines( 495 "test.SimpleScope", 496 "package test;", 497 "", 498 "import javax.inject.Scope;", 499 "", 500 "@Scope @interface SimpleScope {}"); 501 JavaFileObject scopeA = 502 JavaFileObjects.forSourceLines( 503 "test.ScopeA", 504 "package test;", 505 "", 506 "import javax.inject.Scope;", 507 "", 508 "@Scope @interface ScopeA {}"); 509 JavaFileObject scopeB = 510 JavaFileObjects.forSourceLines( 511 "test.ScopeB", 512 "package test;", 513 "", 514 "import javax.inject.Scope;", 515 "", 516 "@Scope @interface ScopeB {}"); 517 JavaFileObject componentA = 518 JavaFileObjects.forSourceLines( 519 "test.ComponentA", 520 "package test;", 521 "", 522 "import dagger.Component;", 523 "", 524 "@ScopeA", 525 "@Component", 526 "interface ComponentA {", 527 " SimpleType type();", 528 "}"); 529 JavaFileObject componentB1 = 530 JavaFileObjects.forSourceLines( 531 "test.ComponentB1", 532 "package test;", 533 "", 534 "import dagger.Component;", 535 "", 536 "@ScopeB", 537 "@Component(dependencies = ComponentA.class)", 538 "interface ComponentB1 {", 539 " SimpleType type();", 540 "}"); 541 JavaFileObject componentB2 = 542 JavaFileObjects.forSourceLines( 543 "test.ComponentB2", 544 "package test;", 545 "", 546 "import dagger.Component;", 547 "", 548 "@ScopeB", 549 "@Component(dependencies = ComponentA.class)", 550 "interface ComponentB2 {", 551 "}"); 552 JavaFileObject componentC = 553 JavaFileObjects.forSourceLines( 554 "test.ComponentC", 555 "package test;", 556 "", 557 "import dagger.Component;", 558 "", 559 "@SimpleScope", 560 "@Component(dependencies = {ComponentB1.class, ComponentB2.class})", 561 "interface ComponentC {", 562 " SimpleType type();", 563 "}"); 564 565 Compilation compilation = 566 daggerCompiler() 567 .compile( 568 type, simpleScope, scopeA, scopeB, 569 componentA, componentB1, componentB2, componentC); 570 assertThat(compilation).succeededWithoutWarnings(); 571 } 572 573 @Test componentWithoutScopeCannotDependOnScopedComponent()574 public void componentWithoutScopeCannotDependOnScopedComponent() { 575 JavaFileObject type = 576 JavaFileObjects.forSourceLines( 577 "test.SimpleType", 578 "package test;", 579 "", 580 "import javax.inject.Inject;", 581 "", 582 "class SimpleType {", 583 " @Inject SimpleType() {}", 584 "}"); 585 JavaFileObject scopedComponent = 586 JavaFileObjects.forSourceLines( 587 "test.ScopedComponent", 588 "package test;", 589 "", 590 "import dagger.Component;", 591 "import javax.inject.Singleton;", 592 "", 593 "@Singleton", 594 "@Component", 595 "interface ScopedComponent {", 596 " SimpleType type();", 597 "}"); 598 JavaFileObject unscopedComponent = 599 JavaFileObjects.forSourceLines( 600 "test.UnscopedComponent", 601 "package test;", 602 "", 603 "import dagger.Component;", 604 "import javax.inject.Singleton;", 605 "", 606 "@Component(dependencies = ScopedComponent.class)", 607 "interface UnscopedComponent {", 608 " SimpleType type();", 609 "}"); 610 611 Compilation compilation = daggerCompiler().compile(type, scopedComponent, unscopedComponent); 612 assertThat(compilation).failed(); 613 assertThat(compilation) 614 .hadErrorContaining( 615 message( 616 "test.UnscopedComponent (unscoped) cannot depend on scoped components:", 617 " @Singleton test.ScopedComponent")); 618 } 619 620 @Test componentWithSingletonScopeMayNotDependOnOtherScope()621 public void componentWithSingletonScopeMayNotDependOnOtherScope() { 622 // Singleton must be the widest lifetime of present scopes. 623 JavaFileObject type = 624 JavaFileObjects.forSourceLines( 625 "test.SimpleType", 626 "package test;", 627 "", 628 "import javax.inject.Inject;", 629 "", 630 "class SimpleType {", 631 " @Inject SimpleType() {}", 632 "}"); 633 JavaFileObject simpleScope = 634 JavaFileObjects.forSourceLines( 635 "test.SimpleScope", 636 "package test;", 637 "", 638 "import javax.inject.Scope;", 639 "", 640 "@Scope @interface SimpleScope {}"); 641 JavaFileObject simpleScoped = 642 JavaFileObjects.forSourceLines( 643 "test.SimpleScopedComponent", 644 "package test;", 645 "", 646 "import dagger.Component;", 647 "", 648 "@SimpleScope", 649 "@Component", 650 "interface SimpleScopedComponent {", 651 " SimpleType type();", 652 "}"); 653 JavaFileObject singletonScoped = 654 JavaFileObjects.forSourceLines( 655 "test.SingletonComponent", 656 "package test;", 657 "", 658 "import dagger.Component;", 659 "import javax.inject.Singleton;", 660 "", 661 "@Singleton", 662 "@Component(dependencies = SimpleScopedComponent.class)", 663 "interface SingletonComponent {", 664 " SimpleType type();", 665 "}"); 666 667 Compilation compilation = 668 daggerCompiler().compile(type, simpleScope, simpleScoped, singletonScoped); 669 assertThat(compilation).failed(); 670 assertThat(compilation) 671 .hadErrorContaining( 672 message( 673 "This @Singleton component cannot depend on scoped components:", 674 " @test.SimpleScope test.SimpleScopedComponent")); 675 } 676 677 @Test componentScopeWithMultipleScopedDependenciesMustNotCycle()678 public void componentScopeWithMultipleScopedDependenciesMustNotCycle() { 679 JavaFileObject type = 680 JavaFileObjects.forSourceLines( 681 "test.SimpleType", 682 "package test;", 683 "", 684 "import javax.inject.Inject;", 685 "", 686 "class SimpleType {", 687 " @Inject SimpleType() {}", 688 "}"); 689 JavaFileObject scopeA = 690 JavaFileObjects.forSourceLines( 691 "test.ScopeA", 692 "package test;", 693 "", 694 "import javax.inject.Scope;", 695 "", 696 "@Scope @interface ScopeA {}"); 697 JavaFileObject scopeB = 698 JavaFileObjects.forSourceLines( 699 "test.ScopeB", 700 "package test;", 701 "", 702 "import javax.inject.Scope;", 703 "", 704 "@Scope @interface ScopeB {}"); 705 JavaFileObject longLifetime = 706 JavaFileObjects.forSourceLines( 707 "test.ComponentLong", 708 "package test;", 709 "", 710 "import dagger.Component;", 711 "", 712 "@ScopeA", 713 "@Component", 714 "interface ComponentLong {", 715 " SimpleType type();", 716 "}"); 717 JavaFileObject mediumLifetime1 = 718 JavaFileObjects.forSourceLines( 719 "test.ComponentMedium1", 720 "package test;", 721 "", 722 "import dagger.Component;", 723 "", 724 "@ScopeB", 725 "@Component(dependencies = ComponentLong.class)", 726 "interface ComponentMedium1 {", 727 " SimpleType type();", 728 "}"); 729 JavaFileObject mediumLifetime2 = 730 JavaFileObjects.forSourceLines( 731 "test.ComponentMedium2", 732 "package test;", 733 "", 734 "import dagger.Component;", 735 "", 736 "@ScopeB", 737 "@Component", 738 "interface ComponentMedium2 {", 739 "}"); 740 JavaFileObject shortLifetime = 741 JavaFileObjects.forSourceLines( 742 "test.ComponentShort", 743 "package test;", 744 "", 745 "import dagger.Component;", 746 "", 747 "@ScopeA", 748 "@Component(dependencies = {ComponentMedium1.class, ComponentMedium2.class})", 749 "interface ComponentShort {", 750 " SimpleType type();", 751 "}"); 752 753 Compilation compilation = 754 daggerCompiler() 755 .compile( 756 type, scopeA, scopeB, 757 longLifetime, mediumLifetime1, mediumLifetime2, shortLifetime); 758 assertThat(compilation).failed(); 759 assertThat(compilation) 760 .hadErrorContaining( 761 message( 762 "test.ComponentShort depends on scoped components in a non-hierarchical scope " 763 + "ordering:", 764 " @test.ScopeA test.ComponentLong", 765 " @test.ScopeB test.ComponentMedium1", 766 " @test.ScopeA test.ComponentShort")); 767 } 768 769 @Test componentScopeAncestryMustNotCycle()770 public void componentScopeAncestryMustNotCycle() { 771 // The dependency relationship of components is necessarily from shorter lifetimes to 772 // longer lifetimes. The scoping annotations must reflect this, and so one cannot declare 773 // scopes on components such that they cycle. 774 JavaFileObject type = 775 JavaFileObjects.forSourceLines( 776 "test.SimpleType", 777 "package test;", 778 "", 779 "import javax.inject.Inject;", 780 "", 781 "class SimpleType {", 782 " @Inject SimpleType() {}", 783 "}"); 784 JavaFileObject scopeA = 785 JavaFileObjects.forSourceLines( 786 "test.ScopeA", 787 "package test;", 788 "", 789 "import javax.inject.Scope;", 790 "", 791 "@Scope @interface ScopeA {}"); 792 JavaFileObject scopeB = 793 JavaFileObjects.forSourceLines( 794 "test.ScopeB", 795 "package test;", 796 "", 797 "import javax.inject.Scope;", 798 "", 799 "@Scope @interface ScopeB {}"); 800 JavaFileObject longLifetime = 801 JavaFileObjects.forSourceLines( 802 "test.ComponentLong", 803 "package test;", 804 "", 805 "import dagger.Component;", 806 "", 807 "@ScopeA", 808 "@Component", 809 "interface ComponentLong {", 810 " SimpleType type();", 811 "}"); 812 JavaFileObject mediumLifetime = 813 JavaFileObjects.forSourceLines( 814 "test.ComponentMedium", 815 "package test;", 816 "", 817 "import dagger.Component;", 818 "", 819 "@ScopeB", 820 "@Component(dependencies = ComponentLong.class)", 821 "interface ComponentMedium {", 822 " SimpleType type();", 823 "}"); 824 JavaFileObject shortLifetime = 825 JavaFileObjects.forSourceLines( 826 "test.ComponentShort", 827 "package test;", 828 "", 829 "import dagger.Component;", 830 "", 831 "@ScopeA", 832 "@Component(dependencies = ComponentMedium.class)", 833 "interface ComponentShort {", 834 " SimpleType type();", 835 "}"); 836 837 Compilation compilation = 838 daggerCompiler().compile(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime); 839 assertThat(compilation).failed(); 840 assertThat(compilation) 841 .hadErrorContaining( 842 message( 843 "test.ComponentShort depends on scoped components in a non-hierarchical scope " 844 + "ordering:", 845 " @test.ScopeA test.ComponentLong", 846 " @test.ScopeB test.ComponentMedium", 847 " @test.ScopeA test.ComponentShort")); 848 849 850 // Test that compilation succeeds when transitive validation is disabled because the scope cycle 851 // cannot be detected. 852 compilation = 853 compilerWithOptions("-Adagger.validateTransitiveComponentDependencies=DISABLED") 854 .compile(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime); 855 assertThat(compilation).succeeded(); 856 } 857 858 @Test reusableNotAllowedOnComponent()859 public void reusableNotAllowedOnComponent() { 860 JavaFileObject someComponent = 861 JavaFileObjects.forSourceLines( 862 "test.SomeComponent", 863 "package test;", 864 "", 865 "import dagger.Component;", 866 "import dagger.Reusable;", 867 "", 868 "@Reusable", 869 "@Component", 870 "interface SomeComponent {}"); 871 Compilation compilation = daggerCompiler().compile(someComponent); 872 assertThat(compilation).failed(); 873 assertThat(compilation) 874 .hadErrorContaining("@Reusable cannot be applied to components or subcomponents") 875 .inFile(someComponent) 876 .onLine(6); 877 } 878 879 @Test reusableNotAllowedOnSubcomponent()880 public void reusableNotAllowedOnSubcomponent() { 881 JavaFileObject someSubcomponent = 882 JavaFileObjects.forSourceLines( 883 "test.SomeComponent", 884 "package test;", 885 "", 886 "import dagger.Reusable;", 887 "import dagger.Subcomponent;", 888 "", 889 "@Reusable", 890 "@Subcomponent", 891 "interface SomeSubcomponent {}"); 892 Compilation compilation = daggerCompiler().compile(someSubcomponent); 893 assertThat(compilation).failed(); 894 assertThat(compilation) 895 .hadErrorContaining("@Reusable cannot be applied to components or subcomponents") 896 .inFile(someSubcomponent) 897 .onLine(6); 898 } 899 } 900