1 /* 2 * Copyright (C) 2014 Google, Inc. 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 package dagger.internal.codegen; 17 18 import com.google.common.base.Joiner; 19 import com.google.common.collect.ImmutableList; 20 import com.google.testing.compile.JavaFileObjects; 21 import java.util.Arrays; 22 import javax.tools.JavaFileObject; 23 import org.junit.Ignore; 24 import org.junit.Test; 25 import org.junit.runner.RunWith; 26 import org.junit.runners.JUnit4; 27 28 import static com.google.common.truth.Truth.assertAbout; 29 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; 30 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; 31 import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable; 32 33 @RunWith(JUnit4.class) 34 public class GraphValidationTest { 35 private final JavaFileObject NULLABLE = JavaFileObjects.forSourceLines("test.Nullable", 36 "package test;", 37 "public @interface Nullable {}"); 38 componentOnConcreteClass()39 @Test public void componentOnConcreteClass() { 40 JavaFileObject component = JavaFileObjects.forSourceLines("test.MyComponent", 41 "package test;", 42 "", 43 "import dagger.Component;", 44 "", 45 "@Component", 46 "interface MyComponent {", 47 " Foo getFoo();", 48 "}"); 49 JavaFileObject injectable = JavaFileObjects.forSourceLines("test.Foo", 50 "package test;", 51 "", 52 "import javax.inject.Inject;", 53 "", 54 "class Foo {", 55 " @Inject Foo(Bar bar) {}", 56 "}"); 57 JavaFileObject nonInjectable = JavaFileObjects.forSourceLines("test.Bar", 58 "package test;", 59 "", 60 "import javax.inject.Inject;", 61 "", 62 "interface Bar {}"); 63 assertAbout(javaSources()).that(Arrays.asList(component, injectable, nonInjectable)) 64 .processedWith(new ComponentProcessor()) 65 .failsToCompile() 66 .withErrorContaining("test.Bar cannot be provided without an @Provides-annotated method.") 67 .in(component).onLine(7); 68 } 69 componentProvisionWithNoDependencyChain()70 @Test public void componentProvisionWithNoDependencyChain() { 71 JavaFileObject component = 72 JavaFileObjects.forSourceLines( 73 "test.TestClass", 74 "package test;", 75 "", 76 "import dagger.Component;", 77 "import javax.inject.Qualifier;", 78 "", 79 "final class TestClass {", 80 " @Qualifier @interface Q {}", 81 " interface A {}", 82 "", 83 " @Component()", 84 " interface AComponent {", 85 " A getA();", 86 " @Q A qualifiedA();", 87 " }", 88 "}"); 89 assertAbout(javaSource()) 90 .that(component) 91 .processedWith(new ComponentProcessor()) 92 .failsToCompile() 93 .withErrorContaining( 94 "test.TestClass.A cannot be provided without an @Provides-annotated method.") 95 .in(component) 96 .onLine(12) 97 .and() 98 .withErrorContaining( 99 "@test.TestClass.Q test.TestClass.A " 100 + "cannot be provided without an @Provides-annotated method.") 101 .in(component) 102 .onLine(13); 103 } 104 constructorInjectionWithoutAnnotation()105 @Test public void constructorInjectionWithoutAnnotation() { 106 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 107 "package test;", 108 "", 109 "import dagger.Component;", 110 "import dagger.Module;", 111 "import dagger.Provides;", 112 "import javax.inject.Inject;", 113 "", 114 "final class TestClass {", 115 " static class A {", 116 " A() {}", 117 " }", 118 "", 119 " @Component()", 120 " interface AComponent {", 121 " A getA();", 122 " }", 123 "}"); 124 String expectedError = "test.TestClass.A cannot be provided without an " 125 + "@Inject constructor or from an @Provides-annotated method."; 126 assertAbout(javaSource()).that(component) 127 .processedWith(new ComponentProcessor()) 128 .failsToCompile() 129 .withErrorContaining(expectedError).in(component).onLine(15); 130 } 131 membersInjectWithoutProvision()132 @Test public void membersInjectWithoutProvision() { 133 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 134 "package test;", 135 "", 136 "import dagger.Component;", 137 "import dagger.Module;", 138 "import dagger.Provides;", 139 "import javax.inject.Inject;", 140 "", 141 "final class TestClass {", 142 " static class A {", 143 " @Inject A() {}", 144 " }", 145 "", 146 " static class B {", 147 " @Inject A a;", 148 " }", 149 "", 150 " @Component()", 151 " interface AComponent {", 152 " B getB();", 153 " }", 154 "}"); 155 String expectedError = "test.TestClass.B cannot be provided without an " 156 + "@Inject constructor or from an @Provides-annotated method. " 157 + "This type supports members injection but cannot be implicitly provided."; 158 assertAbout(javaSource()).that(component) 159 .processedWith(new ComponentProcessor()) 160 .failsToCompile() 161 .withErrorContaining(expectedError).in(component).onLine(19); 162 } 163 cyclicDependency()164 @Test public void cyclicDependency() { 165 JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", 166 "package test;", 167 "", 168 "import dagger.Component;", 169 "import dagger.Module;", 170 "import dagger.Provides;", 171 "import javax.inject.Inject;", 172 "", 173 "final class Outer {", 174 " static class A {", 175 " @Inject A(C cParam) {}", 176 " }", 177 "", 178 " static class B {", 179 " @Inject B(A aParam) {}", 180 " }", 181 "", 182 " static class C {", 183 " @Inject C(B bParam) {}", 184 " }", 185 "", 186 " @Component()", 187 " interface CComponent {", 188 " C getC();", 189 " }", 190 "}"); 191 192 String expectedError = "test.Outer.CComponent.getC() contains a dependency cycle:\n" 193 + " test.Outer.C.<init>(test.Outer.B bParam)\n" 194 + " [parameter: test.Outer.B bParam]\n" 195 + " test.Outer.B.<init>(test.Outer.A aParam)\n" 196 + " [parameter: test.Outer.A aParam]\n" 197 + " test.Outer.A.<init>(test.Outer.C cParam)\n" 198 + " [parameter: test.Outer.C cParam]"; 199 200 assertAbout(javaSource()).that(component) 201 .processedWith(new ComponentProcessor()) 202 .failsToCompile() 203 .withErrorContaining(expectedError).in(component).onLine(23); 204 } 205 cyclicDependencyNotIncludingEntryPoint()206 @Test public void cyclicDependencyNotIncludingEntryPoint() { 207 JavaFileObject component = 208 JavaFileObjects.forSourceLines( 209 "test.Outer", 210 "package test;", 211 "", 212 "import dagger.Component;", 213 "import dagger.Module;", 214 "import dagger.Provides;", 215 "import javax.inject.Inject;", 216 "", 217 "final class Outer {", 218 " static class A {", 219 " @Inject A(C cParam) {}", 220 " }", 221 "", 222 " static class B {", 223 " @Inject B(A aParam) {}", 224 " }", 225 "", 226 " static class C {", 227 " @Inject C(B bParam) {}", 228 " }", 229 "", 230 " static class D {", 231 " @Inject D(C cParam) {}", 232 " }", 233 "", 234 " @Component()", 235 " interface DComponent {", 236 " D getD();", 237 " }", 238 "}"); 239 240 String expectedError = "test.Outer.DComponent.getD() contains a dependency cycle:\n" 241 + " test.Outer.D.<init>(test.Outer.C cParam)\n" 242 + " [parameter: test.Outer.C cParam]\n" 243 + " test.Outer.C.<init>(test.Outer.B bParam)\n" 244 + " [parameter: test.Outer.B bParam]\n" 245 + " test.Outer.B.<init>(test.Outer.A aParam)\n" 246 + " [parameter: test.Outer.A aParam]\n" 247 + " test.Outer.A.<init>(test.Outer.C cParam)\n" 248 + " [parameter: test.Outer.C cParam]"; 249 250 assertAbout(javaSource()) 251 .that(component) 252 .processedWith(new ComponentProcessor()) 253 .failsToCompile() 254 .withErrorContaining(expectedError) 255 .in(component) 256 .onLine(27); 257 } 258 259 @Test cyclicDependencyNotBrokenByMapBinding()260 public void cyclicDependencyNotBrokenByMapBinding() { 261 JavaFileObject component = 262 JavaFileObjects.forSourceLines( 263 "test.Outer", 264 "package test;", 265 "", 266 "import dagger.Component;", 267 "import dagger.MapKey;", 268 "import dagger.Module;", 269 "import dagger.Provides;", 270 "import java.util.Map;", 271 "import javax.inject.Inject;", 272 "", 273 "final class Outer {", 274 " static class A {", 275 " @Inject A(Map<String, C> cMap) {}", 276 " }", 277 "", 278 " static class B {", 279 " @Inject B(A aParam) {}", 280 " }", 281 "", 282 " static class C {", 283 " @Inject C(B bParam) {}", 284 " }", 285 "", 286 " @Component(modules = CModule.class)", 287 " interface CComponent {", 288 " C getC();", 289 " }", 290 "", 291 " @Module", 292 " static class CModule {", 293 " @Provides(type = Provides.Type.MAP)", 294 " @StringKey(\"C\")", 295 " static C c(C c) {", 296 " return c;", 297 " }", 298 " }", 299 "", 300 " @MapKey", 301 " @interface StringKey {", 302 " String value();", 303 " }", 304 "}"); 305 306 String expectedError = 307 Joiner.on('\n') 308 .join( 309 "test.Outer.CComponent.getC() contains a dependency cycle:", 310 " test.Outer.C.<init>(test.Outer.B bParam)", 311 " [parameter: test.Outer.B bParam]", 312 " test.Outer.B.<init>(test.Outer.A aParam)", 313 " [parameter: test.Outer.A aParam]", 314 " test.Outer.A.<init>(java.util.Map<java.lang.String,test.Outer.C> cMap)", 315 " [parameter: java.util.Map<java.lang.String,test.Outer.C> cMap]", 316 " test.Outer.A.<init>(java.util.Map<java.lang.String,test.Outer.C> cMap)", 317 " [parameter: java.util.Map<java.lang.String,test.Outer.C> cMap]", 318 " test.Outer.CModule.c(test.Outer.C c)", 319 " [parameter: test.Outer.C c]"); 320 321 assertAbout(javaSource()) 322 .that(component) 323 .processedWith(new ComponentProcessor()) 324 .failsToCompile() 325 .withErrorContaining(expectedError) 326 .in(component) 327 .onLine(25); 328 } 329 330 @Test falsePositiveCyclicDependencyIndirectionDetected()331 public void falsePositiveCyclicDependencyIndirectionDetected() { 332 JavaFileObject component = 333 JavaFileObjects.forSourceLines( 334 "test.Outer", 335 "package test;", 336 "", 337 "import dagger.Component;", 338 "import dagger.Module;", 339 "import dagger.Provides;", 340 "import javax.inject.Inject;", 341 "import javax.inject.Provider;", 342 "", 343 "final class Outer {", 344 " static class A {", 345 " @Inject A(C cParam) {}", 346 " }", 347 "", 348 " static class B {", 349 " @Inject B(A aParam) {}", 350 " }", 351 "", 352 " static class C {", 353 " @Inject C(B bParam) {}", 354 " }", 355 "", 356 " static class D {", 357 " @Inject D(Provider<C> cParam) {}", 358 " }", 359 "", 360 " @Component()", 361 " interface DComponent {", 362 " D getD();", 363 " }", 364 "}"); 365 366 String expectedError = 367 "test.Outer.DComponent.getD() contains a dependency cycle:\n" 368 + " test.Outer.D.<init>(javax.inject.Provider<test.Outer.C> cParam)\n" 369 + " [parameter: javax.inject.Provider<test.Outer.C> cParam]\n" 370 + " test.Outer.C.<init>(test.Outer.B bParam)\n" 371 + " [parameter: test.Outer.B bParam]\n" 372 + " test.Outer.B.<init>(test.Outer.A aParam)\n" 373 + " [parameter: test.Outer.A aParam]\n" 374 + " test.Outer.A.<init>(test.Outer.C cParam)\n" 375 + " [parameter: test.Outer.C cParam]"; 376 377 assertAbout(javaSource()) 378 .that(component) 379 .processedWith(new ComponentProcessor()) 380 .failsToCompile() 381 .withErrorContaining(expectedError) 382 .in(component) 383 .onLine(28); 384 } 385 cyclicDependencySimpleProviderIndirectionWarning()386 @Ignore @Test public void cyclicDependencySimpleProviderIndirectionWarning() { 387 JavaFileObject component = 388 JavaFileObjects.forSourceLines( 389 "test.Outer", 390 "package test;", 391 "", 392 "import dagger.Component;", 393 "import dagger.Module;", 394 "import dagger.Provides;", 395 "import javax.inject.Inject;", 396 "import javax.inject.Provider;", 397 "", 398 "final class Outer {", 399 " static class A {", 400 " @Inject A(B bParam) {}", 401 " }", 402 "", 403 " static class B {", 404 " @Inject B(C bParam, D dParam) {}", 405 " }", 406 "", 407 " static class C {", 408 " @Inject C(Provider<A> aParam) {}", 409 " }", 410 "", 411 " static class D {", 412 " @Inject D() {}", 413 " }", 414 "", 415 " @Component()", 416 " interface CComponent {", 417 " C get();", 418 " }", 419 "}"); 420 421 /* String expectedWarning = 422 "test.Outer.CComponent.get() contains a dependency cycle:" 423 + " test.Outer.C.<init>(javax.inject.Provider<test.Outer.A> aParam)" 424 + " [parameter: javax.inject.Provider<test.Outer.A> aParam]" 425 + " test.Outer.A.<init>(test.Outer.B bParam)" 426 + " [parameter: test.Outer.B bParam]" 427 + " test.Outer.B.<init>(test.Outer.C bParam, test.Outer.D dParam)" 428 + " [parameter: test.Outer.C bParam]"; 429 */ 430 assertAbout(javaSource()) // TODO(cgruber): Implement warning checks. 431 .that(component) 432 .processedWith(new ComponentProcessor()) 433 .compilesWithoutError(); 434 //.withWarningContaining(expectedWarning).in(component).onLine(X); 435 } 436 cyclicDependencySimpleProviderIndirectionWarningSuppressed()437 @Ignore @Test public void cyclicDependencySimpleProviderIndirectionWarningSuppressed() { 438 JavaFileObject component = 439 JavaFileObjects.forSourceLines( 440 "test.Outer", 441 "package test;", 442 "", 443 "import dagger.Component;", 444 "import dagger.Module;", 445 "import dagger.Provides;", 446 "import javax.inject.Inject;", 447 "import javax.inject.Provider;", 448 "", 449 "final class Outer {", 450 " static class A {", 451 " @Inject A(B bParam) {}", 452 " }", 453 "", 454 " static class B {", 455 " @Inject B(C bParam, D dParam) {}", 456 " }", 457 "", 458 " static class C {", 459 " @Inject C(Provider<A> aParam) {}", 460 " }", 461 "", 462 " static class D {", 463 " @Inject D() {}", 464 " }", 465 "", 466 " @SuppressWarnings(\"dependency-cycle\")", 467 " @Component()", 468 " interface CComponent {", 469 " C get();", 470 " }", 471 "}"); 472 473 assertAbout(javaSource()) 474 .that(component) 475 .processedWith(new ComponentProcessor()) 476 .compilesWithoutError(); 477 //.compilesWithoutWarning(); //TODO(cgruber) 478 } 479 duplicateExplicitBindings_ProvidesAndComponentProvision()480 @Test public void duplicateExplicitBindings_ProvidesAndComponentProvision() { 481 JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", 482 "package test;", 483 "", 484 "import dagger.Component;", 485 "import dagger.Module;", 486 "import dagger.Provides;", 487 "", 488 "final class Outer {", 489 " interface A {}", 490 "", 491 " interface B {}", 492 "", 493 " @Module", 494 " static class AModule {", 495 " @Provides String provideString() { return \"\"; }", 496 " @Provides A provideA(String s) { return new A() {}; }", 497 " }", 498 "", 499 " @Component(modules = AModule.class)", 500 " interface Parent {", 501 " A getA();", 502 " }", 503 "", 504 " @Module", 505 " static class BModule {", 506 " @Provides B provideB(A a) { return new B() {}; }", 507 " }", 508 "", 509 " @Component(dependencies = Parent.class, modules = { BModule.class, AModule.class})", 510 " interface Child {", 511 " B getB();", 512 " }", 513 "}"); 514 515 String expectedError = "test.Outer.A is bound multiple times:\n" 516 + " test.Outer.A test.Outer.Parent.getA()\n" 517 + " @Provides test.Outer.A test.Outer.AModule.provideA(String)"; 518 519 assertAbout(javaSource()).that(component) 520 .processedWith(new ComponentProcessor()) 521 .failsToCompile() 522 .withErrorContaining(expectedError).in(component).onLine(30); 523 } 524 duplicateExplicitBindings_TwoProvidesMethods()525 @Test public void duplicateExplicitBindings_TwoProvidesMethods() { 526 JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", 527 "package test;", 528 "", 529 "import dagger.Component;", 530 "import dagger.Module;", 531 "import dagger.Provides;", 532 "import javax.inject.Inject;", 533 "", 534 "final class Outer {", 535 " interface A {}", 536 "", 537 " @Module", 538 " static class Module1 {", 539 " @Provides A provideA1() { return new A() {}; }", 540 " }", 541 "", 542 " @Module", 543 " static class Module2 {", 544 " @Provides String provideString() { return \"\"; }", 545 " @Provides A provideA2(String s) { return new A() {}; }", 546 " }", 547 "", 548 " @Component(modules = { Module1.class, Module2.class})", 549 " interface TestComponent {", 550 " A getA();", 551 " }", 552 "}"); 553 554 String expectedError = "test.Outer.A is bound multiple times:\n" 555 + " @Provides test.Outer.A test.Outer.Module1.provideA1()\n" 556 + " @Provides test.Outer.A test.Outer.Module2.provideA2(String)"; 557 558 assertAbout(javaSource()).that(component) 559 .processedWith(new ComponentProcessor()) 560 .failsToCompile() 561 .withErrorContaining(expectedError).in(component).onLine(24); 562 } 563 duplicateExplicitBindings_MultipleProvisionTypes()564 @Test public void duplicateExplicitBindings_MultipleProvisionTypes() { 565 JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", 566 "package test;", 567 "", 568 "import dagger.Component;", 569 "import dagger.MapKey;", 570 "import dagger.Module;", 571 "import dagger.Provides;", 572 "import dagger.MapKey;", 573 "import java.util.HashMap;", 574 "import java.util.HashSet;", 575 "import java.util.Map;", 576 "import java.util.Set;", 577 "", 578 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 579 "import static dagger.Provides.Type.MAP;", 580 "import static dagger.Provides.Type.SET;", 581 "", 582 "final class Outer {", 583 " @MapKey(unwrapValue = true)", 584 " @interface StringKey {", 585 " String value();", 586 " }", 587 "", 588 " @Module", 589 " static class TestModule1 {", 590 " @Provides(type = MAP)", 591 " @StringKey(\"foo\")", 592 " String stringMapEntry() { return \"\"; }", 593 "", 594 " @Provides(type = SET) String stringSetElement() { return \"\"; }", 595 " }", 596 "", 597 " @Module", 598 " static class TestModule2 {", 599 " @Provides Set<String> stringSet() { return new HashSet<String>(); }", 600 "", 601 " @Provides Map<String, String> stringMap() {", 602 " return new HashMap<String, String>();", 603 " }", 604 " }", 605 "", 606 " @Component(modules = { TestModule1.class, TestModule2.class })", 607 " interface TestComponent {", 608 " Set<String> getStringSet();", 609 " Map<String, String> getStringMap();", 610 " }", 611 "}"); 612 613 String expectedSetError = 614 "java.util.Set<java.lang.String> has incompatible bindings:\n" 615 + " Set bindings:\n" 616 + " @Provides(type=SET) String test.Outer.TestModule1.stringSetElement()\n" 617 + " Unique bindings:\n" 618 + " @Provides Set<String> test.Outer.TestModule2.stringSet()"; 619 620 String expectedMapError = 621 "java.util.Map<java.lang.String,java.lang.String> has incompatible bindings:\n" 622 + " Map bindings:\n" 623 + " @Provides(type=MAP) @test.Outer.StringKey(\"foo\") String" 624 + " test.Outer.TestModule1.stringMapEntry()\n" 625 + " Unique bindings:\n" 626 + " @Provides Map<String,String> test.Outer.TestModule2.stringMap()"; 627 628 assertAbout(javaSource()).that(component) 629 .processedWith(new ComponentProcessor()) 630 .failsToCompile() 631 .withErrorContaining(expectedSetError).in(component).onLine(43) 632 .and().withErrorContaining(expectedMapError).in(component).onLine(44); 633 } 634 duplicateBindings_TruncateAfterLimit()635 @Test public void duplicateBindings_TruncateAfterLimit() { 636 JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", 637 "package test;", 638 "", 639 "import dagger.Component;", 640 "import dagger.Module;", 641 "import dagger.Provides;", 642 "import javax.inject.Inject;", 643 "", 644 "final class Outer {", 645 " interface A {}", 646 "", 647 " @Module", 648 " static class Module1 {", 649 " @Provides A provideA() { return new A() {}; }", 650 " }", 651 "", 652 " @Module", 653 " static class Module2 {", 654 " @Provides A provideA() { return new A() {}; }", 655 " }", 656 "", 657 " @Module", 658 " static class Module3 {", 659 " @Provides A provideA() { return new A() {}; }", 660 " }", 661 "", 662 " @Module", 663 " static class Module4 {", 664 " @Provides A provideA() { return new A() {}; }", 665 " }", 666 "", 667 " @Module", 668 " static class Module5 {", 669 " @Provides A provideA() { return new A() {}; }", 670 " }", 671 "", 672 " @Module", 673 " static class Module6 {", 674 " @Provides A provideA() { return new A() {}; }", 675 " }", 676 "", 677 " @Module", 678 " static class Module7 {", 679 " @Provides A provideA() { return new A() {}; }", 680 " }", 681 "", 682 " @Module", 683 " static class Module8 {", 684 " @Provides A provideA() { return new A() {}; }", 685 " }", 686 "", 687 " @Module", 688 " static class Module9 {", 689 " @Provides A provideA() { return new A() {}; }", 690 " }", 691 "", 692 " @Module", 693 " static class Module10 {", 694 " @Provides A provideA() { return new A() {}; }", 695 " }", 696 "", 697 " @Module", 698 " static class Module11 {", 699 " @Provides A provideA() { return new A() {}; }", 700 " }", 701 "", 702 " @Module", 703 " static class Module12 {", 704 " @Provides A provideA() { return new A() {}; }", 705 " }", 706 "", 707 " @Component(modules = {", 708 " Module1.class,", 709 " Module2.class,", 710 " Module3.class,", 711 " Module4.class,", 712 " Module5.class,", 713 " Module6.class,", 714 " Module7.class,", 715 " Module8.class,", 716 " Module9.class,", 717 " Module10.class,", 718 " Module11.class,", 719 " Module12.class", 720 " })", 721 " interface TestComponent {", 722 " A getA();", 723 " }", 724 "}"); 725 726 String expectedError = "test.Outer.A is bound multiple times:\n" 727 + " @Provides test.Outer.A test.Outer.Module1.provideA()\n" 728 + " @Provides test.Outer.A test.Outer.Module2.provideA()\n" 729 + " @Provides test.Outer.A test.Outer.Module3.provideA()\n" 730 + " @Provides test.Outer.A test.Outer.Module4.provideA()\n" 731 + " @Provides test.Outer.A test.Outer.Module5.provideA()\n" 732 + " @Provides test.Outer.A test.Outer.Module6.provideA()\n" 733 + " @Provides test.Outer.A test.Outer.Module7.provideA()\n" 734 + " @Provides test.Outer.A test.Outer.Module8.provideA()\n" 735 + " @Provides test.Outer.A test.Outer.Module9.provideA()\n" 736 + " @Provides test.Outer.A test.Outer.Module10.provideA()\n" 737 + " and 2 others"; 738 739 assertAbout(javaSource()).that(component) 740 .processedWith(new ComponentProcessor()) 741 .failsToCompile() 742 .withErrorContaining(expectedError).in(component).onLine(86); 743 } 744 longChainOfDependencies()745 @Test public void longChainOfDependencies() { 746 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 747 "package test;", 748 "", 749 "import dagger.Component;", 750 "import dagger.Module;", 751 "import dagger.Provides;", 752 "import javax.inject.Inject;", 753 "", 754 "final class TestClass {", 755 " interface A {}", 756 "", 757 " static class B {", 758 " @Inject B(A a) {}", 759 " }", 760 "", 761 " static class C {", 762 " @Inject B b;", 763 " @Inject C(B b) {}", 764 " }", 765 "", 766 " interface D { }", 767 "", 768 " static class DImpl implements D {", 769 " @Inject DImpl(C c, B b) {}", 770 " }", 771 "", 772 " @Module", 773 " static class DModule {", 774 " @Provides D d(DImpl impl) { return impl; }", 775 " }", 776 "", 777 " @Component(modules = { DModule.class })", 778 " interface AComponent {", 779 " D getFoo();", 780 " C injectC(C c);", 781 " }", 782 "}"); 783 String errorText = 784 "test.TestClass.A cannot be provided without an @Provides-annotated method.\n"; 785 String firstError = errorText 786 + " test.TestClass.DModule.d(test.TestClass.DImpl impl)\n" 787 + " [parameter: test.TestClass.DImpl impl]\n" 788 + " test.TestClass.DImpl.<init>(test.TestClass.C c, test.TestClass.B b)\n" 789 + " [parameter: test.TestClass.C c]\n" 790 + " test.TestClass.C.b\n" 791 + " [injected field of type: test.TestClass.B b]\n" 792 + " test.TestClass.B.<init>(test.TestClass.A a)\n" 793 + " [parameter: test.TestClass.A a]"; 794 String secondError = errorText 795 + " test.TestClass.C.b\n" 796 + " [injected field of type: test.TestClass.B b]\n" 797 + " test.TestClass.B.<init>(test.TestClass.A a)\n" 798 + " [parameter: test.TestClass.A a]"; 799 assertAbout(javaSource()).that(component) 800 .processedWith(new ComponentProcessor()) 801 .failsToCompile() 802 .withErrorContaining(firstError).in(component).onLine(33) 803 .and().withErrorContaining(secondError).in(component).onLine(34); 804 } 805 resolvedParametersInDependencyTrace()806 @Test public void resolvedParametersInDependencyTrace() { 807 JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic", 808 "package test;", 809 "", 810 "import javax.inject.Inject;", 811 "import javax.inject.Provider;", 812 "", 813 "final class Generic<T> {", 814 " @Inject Generic(T t) {}", 815 "}"); 816 JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass", 817 "package test;", 818 "", 819 "import javax.inject.Inject;", 820 "import java.util.List;", 821 "", 822 "final class TestClass {", 823 " @Inject TestClass(List list) {}", 824 "}"); 825 JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest", 826 "package test;", 827 "", 828 "import javax.inject.Inject;", 829 "", 830 "final class UsesTest {", 831 " @Inject UsesTest(Generic<TestClass> genericTestClass) {}", 832 "}"); 833 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 834 "package test;", 835 "", 836 "import dagger.Component;", 837 "", 838 "@Component", 839 "interface TestComponent {", 840 " UsesTest usesTest();", 841 "}"); 842 String expectedMsg = Joiner.on("\n").join( 843 "java.util.List cannot be provided without an @Provides-annotated method.", 844 " test.UsesTest.<init>(test.Generic<test.TestClass> genericTestClass)", 845 " [parameter: test.Generic<test.TestClass> genericTestClass]", 846 " test.Generic.<init>(test.TestClass t)", 847 " [parameter: test.TestClass t]", 848 " test.TestClass.<init>(java.util.List list)", 849 " [parameter: java.util.List list]"); 850 assertAbout(javaSources()).that(ImmutableList.of(generic, testClass, usesTest, component)) 851 .processedWith(new ComponentProcessor()) 852 .failsToCompile() 853 .withErrorContaining(expectedMsg); 854 } 855 resolvedVariablesInDependencyTrace()856 @Test public void resolvedVariablesInDependencyTrace() { 857 JavaFileObject generic = JavaFileObjects.forSourceLines("test.Generic", 858 "package test;", 859 "", 860 "import javax.inject.Inject;", 861 "import javax.inject.Provider;", 862 "", 863 "final class Generic<T> {", 864 " @Inject T t;", 865 " @Inject Generic() {}", 866 "}"); 867 JavaFileObject testClass = JavaFileObjects.forSourceLines("test.TestClass", 868 "package test;", 869 "", 870 "import javax.inject.Inject;", 871 "import java.util.List;", 872 "", 873 "final class TestClass {", 874 " @Inject TestClass(List list) {}", 875 "}"); 876 JavaFileObject usesTest = JavaFileObjects.forSourceLines("test.UsesTest", 877 "package test;", 878 "", 879 "import javax.inject.Inject;", 880 "", 881 "final class UsesTest {", 882 " @Inject UsesTest(Generic<TestClass> genericTestClass) {}", 883 "}"); 884 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 885 "package test;", 886 "", 887 "import dagger.Component;", 888 "", 889 "@Component", 890 "interface TestComponent {", 891 " UsesTest usesTest();", 892 "}"); 893 String expectedMsg = Joiner.on("\n").join( 894 "java.util.List cannot be provided without an @Provides-annotated method.", 895 " test.UsesTest.<init>(test.Generic<test.TestClass> genericTestClass)", 896 " [parameter: test.Generic<test.TestClass> genericTestClass]", 897 " test.Generic.t", 898 " [injected field of type: test.TestClass t]", 899 " test.TestClass.<init>(java.util.List list)", 900 " [parameter: java.util.List list]"); 901 assertAbout(javaSources()).that(ImmutableList.of(generic, testClass, usesTest, component)) 902 .processedWith(new ComponentProcessor()) 903 .failsToCompile() 904 .withErrorContaining(expectedMsg); 905 } 906 nullCheckForConstructorParameters()907 @Test public void nullCheckForConstructorParameters() { 908 JavaFileObject a = JavaFileObjects.forSourceLines("test.A", 909 "package test;", 910 "", 911 "import javax.inject.Inject;", 912 "", 913 "final class A {", 914 " @Inject A(String string) {}", 915 "}"); 916 JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", 917 "package test;", 918 "", 919 "import dagger.Provides;", 920 "import javax.inject.Inject;", 921 "", 922 "@dagger.Module", 923 "final class TestModule {", 924 " @Nullable @Provides String provideString() { return null; }", 925 "}"); 926 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 927 "package test;", 928 "", 929 "import dagger.Component;", 930 "", 931 "@Component(modules = TestModule.class)", 932 "interface TestComponent {", 933 " A a();", 934 "}"); 935 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 936 .processedWith(new ComponentProcessor()) 937 .failsToCompile() 938 .withErrorContaining( 939 nullableToNonNullable( 940 "java.lang.String", 941 "@test.Nullable @Provides String test.TestModule.provideString()")); 942 943 // but if we disable the validation, then it compiles fine. 944 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 945 .withCompilerOptions("-Adagger.nullableValidation=WARNING") 946 .processedWith(new ComponentProcessor()) 947 .compilesWithoutError(); 948 } 949 nullCheckForMembersInjectParam()950 @Test public void nullCheckForMembersInjectParam() { 951 JavaFileObject a = JavaFileObjects.forSourceLines("test.A", 952 "package test;", 953 "", 954 "import javax.inject.Inject;", 955 "", 956 "final class A {", 957 " @Inject A() {}", 958 " @Inject void register(String string) {}", 959 "}"); 960 JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", 961 "package test;", 962 "", 963 "import dagger.Provides;", 964 "import javax.inject.Inject;", 965 "", 966 "@dagger.Module", 967 "final class TestModule {", 968 " @Nullable @Provides String provideString() { return null; }", 969 "}"); 970 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 971 "package test;", 972 "", 973 "import dagger.Component;", 974 "", 975 "@Component(modules = TestModule.class)", 976 "interface TestComponent {", 977 " A a();", 978 "}"); 979 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 980 .processedWith(new ComponentProcessor()) 981 .failsToCompile() 982 .withErrorContaining( 983 nullableToNonNullable( 984 "java.lang.String", 985 "@test.Nullable @Provides String test.TestModule.provideString()")); 986 987 // but if we disable the validation, then it compiles fine. 988 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 989 .withCompilerOptions("-Adagger.nullableValidation=WARNING") 990 .processedWith(new ComponentProcessor()) 991 .compilesWithoutError(); 992 } 993 nullCheckForVariable()994 @Test public void nullCheckForVariable() { 995 JavaFileObject a = JavaFileObjects.forSourceLines("test.A", 996 "package test;", 997 "", 998 "import javax.inject.Inject;", 999 "", 1000 "final class A {", 1001 " @Inject String string;", 1002 " @Inject A() {}", 1003 "}"); 1004 JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", 1005 "package test;", 1006 "", 1007 "import dagger.Provides;", 1008 "import javax.inject.Inject;", 1009 "", 1010 "@dagger.Module", 1011 "final class TestModule {", 1012 " @Nullable @Provides String provideString() { return null; }", 1013 "}"); 1014 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 1015 "package test;", 1016 "", 1017 "import dagger.Component;", 1018 "", 1019 "@Component(modules = TestModule.class)", 1020 "interface TestComponent {", 1021 " A a();", 1022 "}"); 1023 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 1024 .processedWith(new ComponentProcessor()) 1025 .failsToCompile() 1026 .withErrorContaining( 1027 nullableToNonNullable( 1028 "java.lang.String", 1029 "@test.Nullable @Provides String test.TestModule.provideString()")); 1030 1031 // but if we disable the validation, then it compiles fine. 1032 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, a, module, component)) 1033 .withCompilerOptions("-Adagger.nullableValidation=WARNING") 1034 .processedWith(new ComponentProcessor()) 1035 .compilesWithoutError(); 1036 } 1037 nullCheckForComponentReturn()1038 @Test public void nullCheckForComponentReturn() { 1039 JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", 1040 "package test;", 1041 "", 1042 "import dagger.Provides;", 1043 "import javax.inject.Inject;", 1044 "", 1045 "@dagger.Module", 1046 "final class TestModule {", 1047 " @Nullable @Provides String provideString() { return null; }", 1048 "}"); 1049 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", 1050 "package test;", 1051 "", 1052 "import dagger.Component;", 1053 "", 1054 "@Component(modules = TestModule.class)", 1055 "interface TestComponent {", 1056 " String string();", 1057 "}"); 1058 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component)) 1059 .processedWith(new ComponentProcessor()) 1060 .failsToCompile() 1061 .withErrorContaining( 1062 nullableToNonNullable( 1063 "java.lang.String", 1064 "@test.Nullable @Provides String test.TestModule.provideString()")); 1065 1066 // but if we disable the validation, then it compiles fine. 1067 assertAbout(javaSources()).that(ImmutableList.of(NULLABLE, module, component)) 1068 .withCompilerOptions("-Adagger.nullableValidation=WARNING") 1069 .processedWith(new ComponentProcessor()) 1070 .compilesWithoutError(); 1071 } 1072 componentDependencyMustNotCycle_Direct()1073 @Test public void componentDependencyMustNotCycle_Direct() { 1074 JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort", 1075 "package test;", 1076 "", 1077 "import dagger.Component;", 1078 "", 1079 "@Component(dependencies = ComponentShort.class)", 1080 "interface ComponentShort {", 1081 "}"); 1082 String errorMessage = 1083 "test.ComponentShort contains a cycle in its component dependencies:\n" 1084 + " test.ComponentShort"; 1085 assertAbout(javaSource()) 1086 .that(shortLifetime) 1087 .processedWith(new ComponentProcessor()) 1088 .failsToCompile() 1089 .withErrorContaining(errorMessage); 1090 } 1091 componentDependencyMustNotCycle_Indirect()1092 @Test public void componentDependencyMustNotCycle_Indirect() { 1093 JavaFileObject longLifetime = JavaFileObjects.forSourceLines("test.ComponentLong", 1094 "package test;", 1095 "", 1096 "import dagger.Component;", 1097 "", 1098 "@Component(dependencies = ComponentMedium.class)", 1099 "interface ComponentLong {", 1100 "}"); 1101 JavaFileObject mediumLifetime = JavaFileObjects.forSourceLines("test.ComponentMedium", 1102 "package test;", 1103 "", 1104 "import dagger.Component;", 1105 "", 1106 "@Component(dependencies = ComponentLong.class)", 1107 "interface ComponentMedium {", 1108 "}"); 1109 JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort", 1110 "package test;", 1111 "", 1112 "import dagger.Component;", 1113 "", 1114 "@Component(dependencies = ComponentMedium.class)", 1115 "interface ComponentShort {", 1116 "}"); 1117 String longErrorMessage = 1118 "test.ComponentLong contains a cycle in its component dependencies:\n" 1119 + " test.ComponentLong\n" 1120 + " test.ComponentMedium\n" 1121 + " test.ComponentLong"; 1122 String mediumErrorMessage = 1123 "test.ComponentMedium contains a cycle in its component dependencies:\n" 1124 + " test.ComponentMedium\n" 1125 + " test.ComponentLong\n" 1126 + " test.ComponentMedium"; 1127 String shortErrorMessage = 1128 "test.ComponentShort contains a cycle in its component dependencies:\n" 1129 + " test.ComponentMedium\n" 1130 + " test.ComponentLong\n" 1131 + " test.ComponentMedium\n" 1132 + " test.ComponentShort"; 1133 assertAbout(javaSources()) 1134 .that(ImmutableList.of(longLifetime, mediumLifetime, shortLifetime)) 1135 .processedWith(new ComponentProcessor()) 1136 .failsToCompile() 1137 .withErrorContaining(longErrorMessage).in(longLifetime) 1138 .and() 1139 .withErrorContaining(mediumErrorMessage).in(mediumLifetime) 1140 .and() 1141 .withErrorContaining(shortErrorMessage).in(shortLifetime); 1142 } 1143 } 1144