1 /* 2 * Copyright (C) 2018 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 com.google.testing.compile.Compilation; 24 import com.google.testing.compile.JavaFileObjects; 25 import javax.tools.JavaFileObject; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 import org.junit.runners.JUnit4; 29 30 @RunWith(JUnit4.class) 31 public class MissingBindingValidationTest { 32 @Test dependOnInterface()33 public void dependOnInterface() { 34 JavaFileObject component = JavaFileObjects.forSourceLines("test.MyComponent", 35 "package test;", 36 "", 37 "import dagger.Component;", 38 "", 39 "@Component", 40 "interface MyComponent {", 41 " Foo getFoo();", 42 "}"); 43 JavaFileObject injectable = JavaFileObjects.forSourceLines("test.Foo", 44 "package test;", 45 "", 46 "import javax.inject.Inject;", 47 "", 48 "class Foo {", 49 " @Inject Foo(Bar bar) {}", 50 "}"); 51 JavaFileObject nonInjectable = JavaFileObjects.forSourceLines("test.Bar", 52 "package test;", 53 "", 54 "import javax.inject.Inject;", 55 "", 56 "interface Bar {}"); 57 Compilation compilation = daggerCompiler().compile(component, injectable, nonInjectable); 58 assertThat(compilation).failed(); 59 assertThat(compilation).hadErrorCount(1); 60 assertThat(compilation) 61 .hadErrorContaining("Bar cannot be provided without an @Provides-annotated method.") 62 .inFile(component) 63 .onLineContaining("interface MyComponent"); 64 } 65 66 @Test entryPointDependsOnInterface()67 public void entryPointDependsOnInterface() { 68 JavaFileObject component = 69 JavaFileObjects.forSourceLines( 70 "test.TestClass", 71 "package test;", 72 "", 73 "import dagger.Component;", 74 "", 75 "final class TestClass {", 76 " interface A {}", 77 "", 78 " @Component()", 79 " interface AComponent {", 80 " A getA();", 81 " }", 82 "}"); 83 Compilation compilation = daggerCompiler().compile(component); 84 assertThat(compilation).failed(); 85 assertThat(compilation).hadErrorCount(1); 86 assertThat(compilation) 87 .hadErrorContaining( 88 "\033[1;31m[Dagger/MissingBinding]\033[0m TestClass.A cannot be provided " 89 + "without an @Provides-annotated method.") 90 .inFile(component) 91 .onLineContaining("interface AComponent"); 92 } 93 94 @Test entryPointDependsOnQualifiedInterface()95 public void entryPointDependsOnQualifiedInterface() { 96 JavaFileObject component = 97 JavaFileObjects.forSourceLines( 98 "test.TestClass", 99 "package test;", 100 "", 101 "import dagger.Component;", 102 "import javax.inject.Qualifier;", 103 "", 104 "final class TestClass {", 105 " @Qualifier @interface Q {}", 106 " interface A {}", 107 "", 108 " @Component()", 109 " interface AComponent {", 110 " @Q A qualifiedA();", 111 " }", 112 "}"); 113 Compilation compilation = daggerCompiler().compile(component); 114 assertThat(compilation).failed(); 115 assertThat(compilation).hadErrorCount(1); 116 assertThat(compilation) 117 .hadErrorContaining( 118 "\033[1;31m[Dagger/MissingBinding]\033[0m @TestClass.Q TestClass.A cannot be provided " 119 + "without an @Provides-annotated method.") 120 .inFile(component) 121 .onLineContaining("interface AComponent"); 122 } 123 constructorInjectionWithoutAnnotation()124 @Test public void constructorInjectionWithoutAnnotation() { 125 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 126 "package test;", 127 "", 128 "import dagger.Component;", 129 "import dagger.Module;", 130 "import dagger.Provides;", 131 "import javax.inject.Inject;", 132 "", 133 "final class TestClass {", 134 " static class A {", 135 " A() {}", 136 " }", 137 "", 138 " @Component()", 139 " interface AComponent {", 140 " A getA();", 141 " }", 142 "}"); 143 144 Compilation compilation = daggerCompiler().compile(component); 145 assertThat(compilation).failed(); 146 assertThat(compilation).hadErrorCount(1); 147 assertThat(compilation) 148 .hadErrorContaining( 149 "TestClass.A cannot be provided without an @Inject constructor or an " 150 + "@Provides-annotated method.") 151 .inFile(component) 152 .onLineContaining("interface AComponent"); 153 } 154 membersInjectWithoutProvision()155 @Test public void membersInjectWithoutProvision() { 156 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 157 "package test;", 158 "", 159 "import dagger.Component;", 160 "import dagger.Module;", 161 "import dagger.Provides;", 162 "import javax.inject.Inject;", 163 "", 164 "final class TestClass {", 165 " static class A {", 166 " @Inject A() {}", 167 " }", 168 "", 169 " static class B {", 170 " @Inject A a;", 171 " }", 172 "", 173 " @Component()", 174 " interface AComponent {", 175 " B getB();", 176 " }", 177 "}"); 178 179 Compilation compilation = daggerCompiler().compile(component); 180 assertThat(compilation).failed(); 181 assertThat(compilation).hadErrorCount(1); 182 assertThat(compilation) 183 .hadErrorContaining( 184 "TestClass.B cannot be provided without an @Inject constructor or an " 185 + "@Provides-annotated method. This type supports members injection but cannot be " 186 + "implicitly provided.") 187 .inFile(component) 188 .onLineContaining("interface AComponent"); 189 } 190 191 @Test missingBindingWithSameKeyAsMembersInjectionMethod()192 public void missingBindingWithSameKeyAsMembersInjectionMethod() { 193 JavaFileObject self = 194 JavaFileObjects.forSourceLines( 195 "test.Self", 196 "package test;", 197 "", 198 "import javax.inject.Inject;", 199 "import javax.inject.Provider;", 200 "", 201 "class Self {", 202 " @Inject Provider<Self> selfProvider;", 203 "}"); 204 JavaFileObject component = 205 JavaFileObjects.forSourceLines( 206 "test.SelfComponent", 207 "package test;", 208 "", 209 "import dagger.Component;", 210 "", 211 "@Component", 212 "interface SelfComponent {", 213 " void inject(Self target);", 214 "}"); 215 216 Compilation compilation = daggerCompiler().compile(self, component); 217 assertThat(compilation).failed(); 218 assertThat(compilation).hadErrorCount(1); 219 assertThat(compilation) 220 .hadErrorContaining("Self cannot be provided without an @Inject constructor") 221 .inFile(component) 222 .onLineContaining("interface SelfComponent"); 223 } 224 225 @Test genericInjectClassWithWildcardDependencies()226 public void genericInjectClassWithWildcardDependencies() { 227 JavaFileObject component = 228 JavaFileObjects.forSourceLines( 229 "test.TestComponent", 230 "package test;", 231 "", 232 "import dagger.Component;", 233 "", 234 "@Component", 235 "interface TestComponent {", 236 " Foo<? extends Number> foo();", 237 "}"); 238 JavaFileObject foo = 239 JavaFileObjects.forSourceLines( 240 "test.Foo", 241 "package test;", 242 "", 243 "import javax.inject.Inject;", 244 "", 245 "final class Foo<T> {", 246 " @Inject Foo(T t) {}", 247 "}"); 248 Compilation compilation = daggerCompiler().compile(component, foo); 249 assertThat(compilation).failed(); 250 assertThat(compilation).hadErrorCount(1); 251 assertThat(compilation) 252 .hadErrorContaining( 253 "Foo<? extends Number> cannot be provided " 254 + "without an @Provides-annotated method"); 255 } 256 longChainOfDependencies()257 @Test public void longChainOfDependencies() { 258 JavaFileObject component = 259 JavaFileObjects.forSourceLines( 260 "test.TestClass", 261 "package test;", 262 "", 263 "import dagger.Component;", 264 "import dagger.Lazy;", 265 "import dagger.Module;", 266 "import dagger.Provides;", 267 "import javax.inject.Inject;", 268 "import javax.inject.Named;", 269 "import javax.inject.Provider;", 270 "", 271 "final class TestClass {", 272 " interface A {}", 273 "", 274 " static class B {", 275 " @Inject B(A a) {}", 276 " }", 277 "", 278 " static class C {", 279 " @Inject B b;", 280 " @Inject C(X x) {}", 281 " }", 282 "", 283 " interface D { }", 284 "", 285 " static class DImpl implements D {", 286 " @Inject DImpl(C c, B b) {}", 287 " }", 288 "", 289 " static class X {", 290 " @Inject X() {}", 291 " }", 292 "", 293 " @Module", 294 " static class DModule {", 295 " @Provides @Named(\"slim shady\") D d(X x1, DImpl impl, X x2) { return impl; }", 296 " }", 297 "", 298 " @Component(modules = { DModule.class })", 299 " interface AComponent {", 300 " @Named(\"slim shady\") D getFoo();", 301 " C injectC(C c);", 302 " Provider<C> cProvider();", 303 " Lazy<C> lazyC();", 304 " Provider<Lazy<C>> lazyCProvider();", 305 " }", 306 "}"); 307 308 Compilation compilation = daggerCompiler().compile(component); 309 assertThat(compilation).failed(); 310 assertThat(compilation).hadErrorCount(1); 311 assertThat(compilation) 312 .hadErrorContaining( 313 message( 314 "TestClass.A cannot be provided without an @Provides-annotated method.", 315 " TestClass.A is injected at", 316 " TestClass.B(a)", 317 " TestClass.B is injected at", 318 " TestClass.C.b", 319 " TestClass.C is injected at", 320 " TestClass.AComponent.injectC(TestClass.C)", 321 "The following other entry points also depend on it:", 322 " TestClass.AComponent.getFoo()", 323 " TestClass.AComponent.cProvider()", 324 " TestClass.AComponent.lazyC()", 325 " TestClass.AComponent.lazyCProvider()")) 326 .inFile(component) 327 .onLineContaining("interface AComponent"); 328 } 329 330 @Test bindsMethodAppearsInTrace()331 public void bindsMethodAppearsInTrace() { 332 JavaFileObject component = 333 JavaFileObjects.forSourceLines( 334 "TestComponent", 335 "import dagger.Component;", 336 "", 337 "@Component(modules = TestModule.class)", 338 "interface TestComponent {", 339 " TestInterface testInterface();", 340 "}"); 341 JavaFileObject interfaceFile = 342 JavaFileObjects.forSourceLines("TestInterface", "interface TestInterface {}"); 343 JavaFileObject implementationFile = 344 JavaFileObjects.forSourceLines( 345 "TestImplementation", 346 "import javax.inject.Inject;", 347 "", 348 "final class TestImplementation implements TestInterface {", 349 " @Inject TestImplementation(String missingBinding) {}", 350 "}"); 351 JavaFileObject module = 352 JavaFileObjects.forSourceLines( 353 "TestModule", 354 "import dagger.Binds;", 355 "import dagger.Module;", 356 "", 357 "@Module", 358 "interface TestModule {", 359 " @Binds abstract TestInterface bindTestInterface(TestImplementation implementation);", 360 "}"); 361 362 Compilation compilation = 363 daggerCompiler().compile(component, module, interfaceFile, implementationFile); 364 assertThat(compilation).failed(); 365 assertThat(compilation).hadErrorCount(1); 366 assertThat(compilation) 367 .hadErrorContaining( 368 message( 369 "String cannot be provided without an @Inject constructor or an " 370 + "@Provides-annotated method.", 371 " String is injected at", 372 " TestImplementation(missingBinding)", 373 " TestImplementation is injected at", 374 " TestModule.bindTestInterface(implementation)", 375 " TestInterface is requested at", 376 " TestComponent.testInterface()")) 377 .inFile(component) 378 .onLineContaining("interface TestComponent"); 379 } 380 resolvedParametersInDependencyTrace()381 @Test public void resolvedParametersInDependencyTrace() { 382 JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic", 383 "package test;", 384 "", 385 "import javax.inject.Inject;", 386 "import javax.inject.Provider;", 387 "", 388 "final class Generic<T> {", 389 " @Inject Generic(T t) {}", 390 "}"); 391 JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass", 392 "package test;", 393 "", 394 "import javax.inject.Inject;", 395 "import java.util.List;", 396 "", 397 "final class TestClass {", 398 " @Inject TestClass(List list) {}", 399 "}"); 400 JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest", 401 "package test;", 402 "", 403 "import javax.inject.Inject;", 404 "", 405 "final class UsesTest {", 406 " @Inject UsesTest(Generic<TestClass> genericTestClass) {}", 407 "}"); 408 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 409 "package test;", 410 "", 411 "import dagger.Component;", 412 "", 413 "@Component", 414 "interface TestComponent {", 415 " UsesTest usesTest();", 416 "}"); 417 418 Compilation compilation = daggerCompiler().compile(generic, testClass, usesTest, component); 419 assertThat(compilation).failed(); 420 assertThat(compilation).hadErrorCount(1); 421 assertThat(compilation) 422 .hadErrorContaining( 423 message( 424 "List cannot be provided without an @Provides-annotated method.", 425 " List is injected at", 426 " TestClass(list)", 427 " TestClass is injected at", 428 " Generic(t)", 429 " Generic<TestClass> is injected at", 430 " UsesTest(genericTestClass)", 431 " UsesTest is requested at", 432 " TestComponent.usesTest()")); 433 } 434 resolvedVariablesInDependencyTrace()435 @Test public void resolvedVariablesInDependencyTrace() { 436 JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic", 437 "package test;", 438 "", 439 "import javax.inject.Inject;", 440 "import javax.inject.Provider;", 441 "", 442 "final class Generic<T> {", 443 " @Inject T t;", 444 " @Inject Generic() {}", 445 "}"); 446 JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass", 447 "package test;", 448 "", 449 "import javax.inject.Inject;", 450 "import java.util.List;", 451 "", 452 "final class TestClass {", 453 " @Inject TestClass(List list) {}", 454 "}"); 455 JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest", 456 "package test;", 457 "", 458 "import javax.inject.Inject;", 459 "", 460 "final class UsesTest {", 461 " @Inject UsesTest(Generic<TestClass> genericTestClass) {}", 462 "}"); 463 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 464 "package test;", 465 "", 466 "import dagger.Component;", 467 "", 468 "@Component", 469 "interface TestComponent {", 470 " UsesTest usesTest();", 471 "}"); 472 473 Compilation compilation = daggerCompiler().compile(generic, testClass, usesTest, component); 474 assertThat(compilation).failed(); 475 assertThat(compilation).hadErrorCount(1); 476 assertThat(compilation) 477 .hadErrorContaining( 478 message( 479 "List cannot be provided without an @Provides-annotated method.", 480 " List is injected at", 481 " TestClass(list)", 482 " TestClass is injected at", 483 " Generic.t", 484 " Generic<TestClass> is injected at", 485 " UsesTest(genericTestClass)", 486 " UsesTest is requested at", 487 " TestComponent.usesTest()")); 488 } 489 490 @Test bindingUsedOnlyInSubcomponentDependsOnBindingOnlyInSubcomponent()491 public void bindingUsedOnlyInSubcomponentDependsOnBindingOnlyInSubcomponent() { 492 JavaFileObject parent = 493 JavaFileObjects.forSourceLines( 494 "Parent", 495 "import dagger.Component;", 496 "", 497 "@Component(modules = ParentModule.class)", 498 "interface Parent {", 499 " Child child();", 500 "}"); 501 JavaFileObject parentModule = 502 JavaFileObjects.forSourceLines( 503 "ParentModule", 504 "import dagger.Module;", 505 "import dagger.Provides;", 506 "", 507 "@Module", 508 "class ParentModule {", 509 " @Provides static Object needsString(String string) {", 510 " return \"needs string: \" + string;", 511 " }", 512 "}"); 513 JavaFileObject child = 514 JavaFileObjects.forSourceLines( 515 "Child", 516 "import dagger.Subcomponent;", 517 "", 518 "@Subcomponent(modules = ChildModule.class)", 519 "interface Child {", 520 " String string();", 521 " Object needsString();", 522 "}"); 523 JavaFileObject childModule = 524 JavaFileObjects.forSourceLines( 525 "ChildModule", 526 "import dagger.Module;", 527 "import dagger.Provides;", 528 "", 529 "@Module", 530 "class ChildModule {", 531 " @Provides static String string() {", 532 " return \"child string\";", 533 " }", 534 "}"); 535 536 Compilation compilation = daggerCompiler().compile(parent, parentModule, child, childModule); 537 assertThat(compilation).failed(); 538 assertThat(compilation).hadErrorCount(1); 539 assertThat(compilation) 540 .hadErrorContainingMatch( 541 "(?s)\\QString cannot be provided\\E.*\\Q[Child] Child.needsString()\\E") 542 .inFile(parent) 543 .onLineContaining("interface Parent"); 544 } 545 546 @Test multibindingContributionBetweenAncestorComponentAndEntrypointComponent()547 public void multibindingContributionBetweenAncestorComponentAndEntrypointComponent() { 548 JavaFileObject parent = 549 JavaFileObjects.forSourceLines( 550 "Parent", 551 "import dagger.Component;", 552 "", 553 "@Component(modules = ParentModule.class)", 554 "interface Parent {", 555 " Child child();", 556 "}"); 557 JavaFileObject child = 558 JavaFileObjects.forSourceLines( 559 "Child", 560 "import dagger.Subcomponent;", 561 "", 562 "@Subcomponent(modules = ChildModule.class)", 563 "interface Child {", 564 " Grandchild grandchild();", 565 "}"); 566 JavaFileObject grandchild = 567 JavaFileObjects.forSourceLines( 568 "Grandchild", 569 "import dagger.Subcomponent;", 570 "", 571 "@Subcomponent", 572 "interface Grandchild {", 573 " Object object();", 574 "}"); 575 576 JavaFileObject parentModule = 577 JavaFileObjects.forSourceLines( 578 "ParentModule", 579 "import dagger.Module;", 580 "import dagger.Provides;", 581 "import dagger.multibindings.IntoSet;", 582 "import java.util.Set;", 583 "", 584 "@Module", 585 "class ParentModule {", 586 " @Provides static Object dependsOnSet(Set<String> strings) {", 587 " return \"needs strings: \" + strings;", 588 " }", 589 "", 590 " @Provides @IntoSet static String contributesToSet() {", 591 " return \"parent string\";", 592 " }", 593 "", 594 " @Provides int missingDependency(double dub) {", 595 " return 4;", 596 " }", 597 "}"); 598 JavaFileObject childModule = 599 JavaFileObjects.forSourceLines( 600 "ChildModule", 601 "import dagger.Module;", 602 "import dagger.Provides;", 603 "import dagger.multibindings.IntoSet;", 604 "", 605 "@Module", 606 "class ChildModule {", 607 " @Provides @IntoSet static String contributesToSet(int i) {", 608 " return \"\" + i;", 609 " }", 610 "}"); 611 Compilation compilation = 612 daggerCompiler().compile(parent, parentModule, child, childModule, grandchild); 613 assertThat(compilation).failed(); 614 assertThat(compilation).hadErrorCount(1); 615 assertThat(compilation) 616 .hadErrorContainingMatch( 617 "(?s)\\QDouble cannot be provided\\E.*" 618 + "\\QGrandchild.object() [Parent → Child → Grandchild]\\E$") 619 .inFile(parent) 620 .onLineContaining("interface Parent"); 621 } 622 623 @Test manyDependencies()624 public void manyDependencies() { 625 JavaFileObject component = 626 JavaFileObjects.forSourceLines( 627 "test.TestComponent", 628 "package test;", 629 "", 630 "import dagger.Component;", 631 "", 632 "@Component(modules = TestModule.class)", 633 "interface TestComponent {", 634 " Object object();", 635 " String string();", 636 "}"); 637 JavaFileObject module = 638 JavaFileObjects.forSourceLines( 639 "test.TestModule", 640 "package test;", 641 "", 642 "import dagger.Binds;", 643 "import dagger.Module;", 644 "import dagger.Provides;", 645 "", 646 "@Module", 647 "abstract class TestModule {", 648 " @Binds abstract Object object(NotBound notBound);", 649 "", 650 " @Provides static String string(NotBound notBound, Object object) {", 651 " return notBound.toString();", 652 " }", 653 "}"); 654 JavaFileObject notBound = 655 JavaFileObjects.forSourceLines( 656 "test.NotBound", // 657 "package test;", 658 "", 659 "interface NotBound {}"); 660 Compilation compilation = daggerCompiler().compile(component, module, notBound); 661 assertThat(compilation).failed(); 662 assertThat(compilation).hadErrorCount(1); 663 assertThat(compilation) 664 .hadErrorContaining( 665 message( 666 "\033[1;31m[Dagger/MissingBinding]\033[0m " 667 + "NotBound cannot be provided without an @Provides-annotated method.", 668 " NotBound is injected at", 669 " TestModule.object(notBound)", 670 " Object is requested at", 671 " TestComponent.object()", 672 "It is also requested at:", 673 " TestModule.string(notBound, …)", 674 "The following other entry points also depend on it:", 675 " TestComponent.string()")) 676 .inFile(component) 677 .onLineContaining("interface TestComponent"); 678 assertThat(compilation).hadErrorCount(1); 679 } 680 681 @Test tooManyRequests()682 public void tooManyRequests() { 683 JavaFileObject foo = 684 JavaFileObjects.forSourceLines( 685 "test.Foo", 686 "package test;", 687 "", 688 "import javax.inject.Inject;", 689 "", 690 "final class Foo {", 691 " @Inject Foo(", 692 " String one,", 693 " String two,", 694 " String three,", 695 " String four,", 696 " String five,", 697 " String six,", 698 " String seven,", 699 " String eight,", 700 " String nine,", 701 " String ten,", 702 " String eleven,", 703 " String twelve,", 704 " String thirteen) {", 705 " }", 706 "}"); 707 JavaFileObject component = 708 JavaFileObjects.forSourceLines( 709 "test.TestComponent", 710 "package test;", 711 "", 712 "import dagger.Component;", 713 "", 714 "@Component", 715 "interface TestComponent {", 716 " String string();", 717 " Foo foo();", 718 "}"); 719 720 Compilation compilation = daggerCompiler().compile(foo, component); 721 assertThat(compilation).failed(); 722 assertThat(compilation).hadErrorCount(1); 723 assertThat(compilation) 724 .hadErrorContaining( 725 message( 726 "\033[1;31m[Dagger/MissingBinding]\033[0m String cannot be provided without an " 727 + "@Inject constructor or an @Provides-annotated method.", 728 " String is requested at", 729 " TestComponent.string()", 730 "It is also requested at:", 731 " Foo(one, …)", 732 " Foo(…, two, …)", 733 " Foo(…, three, …)", 734 " Foo(…, four, …)", 735 " Foo(…, five, …)", 736 " Foo(…, six, …)", 737 " Foo(…, seven, …)", 738 " Foo(…, eight, …)", 739 " Foo(…, nine, …)", 740 " Foo(…, ten, …)", 741 " and 3 others")) 742 .inFile(component) 743 .onLineContaining("interface TestComponent"); 744 } 745 746 @Test tooManyEntryPoints()747 public void tooManyEntryPoints() { 748 JavaFileObject component = 749 JavaFileObjects.forSourceLines( 750 "test.TestComponent", 751 "package test;", 752 "", 753 "import dagger.Component;", 754 "", 755 "@Component", 756 "interface TestComponent {", 757 " String string1();", 758 " String string2();", 759 " String string3();", 760 " String string4();", 761 " String string5();", 762 " String string6();", 763 " String string7();", 764 " String string8();", 765 " String string9();", 766 " String string10();", 767 " String string11();", 768 " String string12();", 769 "}"); 770 771 Compilation compilation = daggerCompiler().compile(component); 772 assertThat(compilation).failed(); 773 assertThat(compilation).hadErrorCount(1); 774 assertThat(compilation) 775 .hadErrorContaining( 776 message( 777 "\033[1;31m[Dagger/MissingBinding]\033[0m String cannot be provided without an " 778 + "@Inject constructor or an @Provides-annotated method.", 779 " String is requested at", 780 " TestComponent.string1()", 781 "The following other entry points also depend on it:", 782 " TestComponent.string2()", 783 " TestComponent.string3()", 784 " TestComponent.string4()", 785 " TestComponent.string5()", 786 " TestComponent.string6()", 787 " TestComponent.string7()", 788 " TestComponent.string8()", 789 " TestComponent.string9()", 790 " TestComponent.string10()", 791 " TestComponent.string11()", 792 " and 1 other")) 793 .inFile(component) 794 .onLineContaining("interface TestComponent"); 795 } 796 797 @Test missingBindingInAllComponentsAndEntryPoints()798 public void missingBindingInAllComponentsAndEntryPoints() { 799 JavaFileObject parent = 800 JavaFileObjects.forSourceLines( 801 "Parent", 802 "import dagger.Component;", 803 "", 804 "@Component", 805 "interface Parent {", 806 " Foo foo();", 807 " Bar bar();", 808 " Child child();", 809 "}"); 810 JavaFileObject child = 811 JavaFileObjects.forSourceLines( 812 "Child", 813 "import dagger.Subcomponent;", 814 "", 815 "@Subcomponent", 816 "interface Child {", 817 " Foo foo();", 818 " Baz baz();", 819 "}"); 820 JavaFileObject foo = 821 JavaFileObjects.forSourceLines( 822 "Foo", 823 "import javax.inject.Inject;", 824 "", 825 "class Foo {", 826 " @Inject Foo(Bar bar) {}", 827 "}"); 828 JavaFileObject bar = 829 JavaFileObjects.forSourceLines( 830 "Bar", 831 "import javax.inject.Inject;", 832 "", 833 "class Bar {", 834 " @Inject Bar(Baz baz) {}", 835 "}"); 836 JavaFileObject baz = JavaFileObjects.forSourceLines("Baz", "class Baz {}"); 837 838 Compilation compilation = daggerCompiler().compile(parent, child, foo, bar, baz); 839 assertThat(compilation).failed(); 840 assertThat(compilation).hadErrorCount(1); 841 assertThat(compilation) 842 .hadErrorContaining( 843 message( 844 "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an " 845 + "@Inject constructor or an @Provides-annotated method.", 846 " Baz is injected at", 847 " Bar(baz)", 848 " Bar is requested at", 849 " Parent.bar()", 850 "The following other entry points also depend on it:", 851 " Parent.foo()", 852 " Child.foo() [Parent → Child]", 853 " Child.baz() [Parent → Child]")) 854 .inFile(parent) 855 .onLineContaining("interface Parent"); 856 } 857 858 // Regression test for b/147423208 where if the same subcomponent was used 859 // in two different parts of the hierarchy and only one side had a missing binding 860 // incorrect caching during binding graph conversion might cause validation to pass 861 // incorrectly. 862 @Test sameSubcomponentUsedInDifferentHierarchies()863 public void sameSubcomponentUsedInDifferentHierarchies() { 864 JavaFileObject parent = JavaFileObjects.forSourceLines("test.Parent", 865 "package test;", 866 "", 867 "import dagger.Component;", 868 "", 869 "@Component", 870 "interface Parent {", 871 " Child1 getChild1();", 872 " Child2 getChild2();", 873 "}"); 874 JavaFileObject child1 = JavaFileObjects.forSourceLines("test.Child1", 875 "package test;", 876 "", 877 "import dagger.Subcomponent;", 878 "", 879 "@Subcomponent(modules = LongModule.class)", 880 "interface Child1 {", 881 " RepeatedSub getSub();", 882 "}"); 883 JavaFileObject child2 = JavaFileObjects.forSourceLines("test.Child2", 884 "package test;", 885 "", 886 "import dagger.Subcomponent;", 887 "", 888 "@Subcomponent", 889 "interface Child2 {", 890 " RepeatedSub getSub();", 891 "}"); 892 JavaFileObject repeatedSub = JavaFileObjects.forSourceLines("test.RepeatedSub", 893 "package test;", 894 "", 895 "import dagger.Subcomponent;", 896 "", 897 "@Subcomponent", 898 "interface RepeatedSub {", 899 " Foo getFoo();", 900 "}"); 901 JavaFileObject injectable = JavaFileObjects.forSourceLines("test.Foo", 902 "package test;", 903 "", 904 "import javax.inject.Inject;", 905 "", 906 "class Foo {", 907 " @Inject Foo(Long value) {}", 908 "}"); 909 JavaFileObject module = JavaFileObjects.forSourceLines("test.LongModule", 910 "package test;", 911 "", 912 "import dagger.Module;", 913 "import dagger.Provides;", 914 "", 915 "@Module", 916 "interface LongModule {", 917 " @Provides static Long provideLong() {", 918 " return 0L;", 919 " }", 920 "}"); 921 Compilation compilation = daggerCompiler().compile( 922 parent, child1, child2, repeatedSub, injectable, module); 923 assertThat(compilation).failed(); 924 assertThat(compilation).hadErrorCount(1); 925 assertThat(compilation) 926 .hadErrorContaining("Long cannot be provided without an @Inject constructor") 927 .inFile(parent) 928 .onLineContaining("interface Parent"); 929 } 930 931 @Test sameSubcomponentUsedInDifferentHierarchiesMissingBindingFromOneSide()932 public void sameSubcomponentUsedInDifferentHierarchiesMissingBindingFromOneSide() { 933 JavaFileObject parent = 934 JavaFileObjects.forSourceLines( 935 "test.Parent", 936 "package test;", 937 "", 938 "import dagger.Component;", 939 "", 940 "@Component", 941 "interface Parent {", 942 " Child1 getChild1();", 943 " Child2 getChild2();", 944 "}"); 945 JavaFileObject child1 = 946 JavaFileObjects.forSourceLines( 947 "test.Child1", 948 "package test;", 949 "", 950 "import dagger.Subcomponent;", 951 "", 952 "@Subcomponent(modules = Child1Module.class)", 953 "interface Child1 {", 954 " RepeatedSub getSub();", 955 "}"); 956 JavaFileObject child2 = 957 JavaFileObjects.forSourceLines( 958 "test.Child2", 959 "package test;", 960 "", 961 "import dagger.Subcomponent;", 962 "", 963 "@Subcomponent(modules = Child2Module.class)", 964 "interface Child2 {", 965 " RepeatedSub getSub();", 966 "}"); 967 JavaFileObject repeatedSub = 968 JavaFileObjects.forSourceLines( 969 "test.RepeatedSub", 970 "package test;", 971 "", 972 "import dagger.Subcomponent;", 973 "", 974 "@Subcomponent(modules = RepeatedSubModule.class)", 975 "interface RepeatedSub {", 976 " Object getObject();", 977 "}"); 978 JavaFileObject child1Module = 979 JavaFileObjects.forSourceLines( 980 "test.Child1Module", 981 "package test;", 982 "", 983 "import dagger.Module;", 984 "import dagger.Provides;", 985 "import java.util.Set;", 986 "import dagger.multibindings.Multibinds;", 987 "", 988 "@Module", 989 "interface Child1Module {", 990 " @Multibinds Set<Integer> multibindIntegerSet();", 991 "}"); 992 JavaFileObject child2Module = 993 JavaFileObjects.forSourceLines( 994 "test.Child2Module", 995 "package test;", 996 "", 997 "import dagger.Module;", 998 "import dagger.Provides;", 999 "import java.util.Set;", 1000 "import dagger.multibindings.Multibinds;", 1001 "", 1002 "@Module", 1003 "interface Child2Module {", 1004 " @Multibinds Set<Integer> multibindIntegerSet();", 1005 "", 1006 " @Provides", 1007 " static Object provideObject(Set<Integer> intSet) {", 1008 " return new Object();", 1009 " }", 1010 "}"); 1011 JavaFileObject repeatedSubModule = 1012 JavaFileObjects.forSourceLines( 1013 "test.RepeatedSubModule", 1014 "package test;", 1015 "", 1016 "import dagger.Module;", 1017 "import dagger.Provides;", 1018 "import dagger.multibindings.IntoSet;", 1019 "import java.util.Set;", 1020 "import dagger.multibindings.Multibinds;", 1021 "", 1022 "@Module", 1023 "interface RepeatedSubModule {", 1024 " @Provides", 1025 " @IntoSet", 1026 " static Integer provideInt() {", 1027 " return 9;", 1028 " }", 1029 "}"); 1030 1031 Compilation compilation = 1032 daggerCompiler() 1033 .compile( 1034 parent, child1, child2, repeatedSub, child1Module, child2Module, repeatedSubModule); 1035 assertThat(compilation).failed(); 1036 assertThat(compilation).hadErrorCount(1); 1037 assertThat(compilation) 1038 .hadErrorContaining("A binding for Object exists in [Parent → Child2 → RepeatedSub]:"); 1039 assertThat(compilation) 1040 .hadErrorContaining( 1041 "[Parent → Child1 → RepeatedSub] RepeatedSub.getObject() [Parent → Child1 →" 1042 + " RepeatedSub]"); 1043 } 1044 1045 @Test differentComponentPkgSameSimpleNameMissingBinding()1046 public void differentComponentPkgSameSimpleNameMissingBinding() { 1047 JavaFileObject parent = 1048 JavaFileObjects.forSourceLines( 1049 "test.Parent", 1050 "package test;", 1051 "", 1052 "import dagger.Component;", 1053 "", 1054 "@Component", 1055 "interface Parent {", 1056 " Child1 getChild1();", 1057 " Child2 getChild2();", 1058 "}"); 1059 JavaFileObject child1 = 1060 JavaFileObjects.forSourceLines( 1061 "test.Child1", 1062 "package test;", 1063 "", 1064 "import dagger.Subcomponent;", 1065 "", 1066 "@Subcomponent(modules = Child1Module.class)", 1067 "interface Child1 {", 1068 " foo.Sub getSub();", 1069 "}"); 1070 JavaFileObject child2 = 1071 JavaFileObjects.forSourceLines( 1072 "test.Child2", 1073 "package test;", 1074 "", 1075 "import dagger.Subcomponent;", 1076 "", 1077 "@Subcomponent(modules = Child2Module.class)", 1078 "interface Child2 {", 1079 " bar.Sub getSub();", 1080 "}"); 1081 JavaFileObject sub1 = 1082 JavaFileObjects.forSourceLines( 1083 "foo.Sub", 1084 "package foo;", 1085 "", 1086 "import dagger.Subcomponent;", 1087 "", 1088 "@Subcomponent(modules = test.RepeatedSubModule.class)", 1089 "public interface Sub {", 1090 " Object getObject();", 1091 "}"); 1092 JavaFileObject sub2 = 1093 JavaFileObjects.forSourceLines( 1094 "bar.Sub", 1095 "package bar;", 1096 "", 1097 "import dagger.Subcomponent;", 1098 "", 1099 "@Subcomponent(modules = test.RepeatedSubModule.class)", 1100 "public interface Sub {", 1101 " Object getObject();", 1102 "}"); 1103 JavaFileObject child1Module = 1104 JavaFileObjects.forSourceLines( 1105 "test.Child1Module", 1106 "package test;", 1107 "", 1108 "import dagger.Module;", 1109 "import dagger.Provides;", 1110 "import java.util.Set;", 1111 "import dagger.multibindings.Multibinds;", 1112 "", 1113 "@Module", 1114 "interface Child1Module {", 1115 " @Multibinds Set<Integer> multibindIntegerSet();", 1116 "}"); 1117 JavaFileObject child2Module = 1118 JavaFileObjects.forSourceLines( 1119 "test.Child2Module", 1120 "package test;", 1121 "", 1122 "import dagger.Module;", 1123 "import dagger.Provides;", 1124 "import java.util.Set;", 1125 "import dagger.multibindings.Multibinds;", 1126 "", 1127 "@Module", 1128 "interface Child2Module {", 1129 " @Multibinds Set<Integer> multibindIntegerSet();", 1130 "", 1131 " @Provides", 1132 " static Object provideObject(Set<Integer> intSet) {", 1133 " return new Object();", 1134 " }", 1135 "}"); 1136 JavaFileObject repeatedSubModule = 1137 JavaFileObjects.forSourceLines( 1138 "test.RepeatedSubModule", 1139 "package test;", 1140 "", 1141 "import dagger.Module;", 1142 "import dagger.Provides;", 1143 "import dagger.multibindings.IntoSet;", 1144 "import java.util.Set;", 1145 "import dagger.multibindings.Multibinds;", 1146 "", 1147 "@Module", 1148 "public interface RepeatedSubModule {", 1149 " @Provides", 1150 " @IntoSet", 1151 " static Integer provideInt() {", 1152 " return 9;", 1153 " }", 1154 "}"); 1155 1156 Compilation compilation = 1157 daggerCompiler() 1158 .compile( 1159 parent, child1, child2, sub1, sub2, child1Module, child2Module, repeatedSubModule); 1160 assertThat(compilation).failed(); 1161 assertThat(compilation).hadErrorCount(1); 1162 assertThat(compilation).hadErrorContaining("A binding for Object exists in bar.Sub:"); 1163 assertThat(compilation) 1164 .hadErrorContaining("[foo.Sub] foo.Sub.getObject() [Parent → Child1 → foo.Sub]"); 1165 } 1166 } 1167