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