1 /* 2 * Copyright (C) 2015 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.collect.ImmutableList; 19 import com.google.testing.compile.JavaFileObjects; 20 import javax.tools.JavaFileObject; 21 import org.junit.Test; 22 import org.junit.runner.RunWith; 23 import org.junit.runners.JUnit4; 24 25 import static com.google.common.truth.Truth.assertAbout; 26 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; 27 28 @RunWith(JUnit4.class) 29 public final class SubcomponentValidationTest { factoryMethod_missingModulesWithParameters()30 @Test public void factoryMethod_missingModulesWithParameters() { 31 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 32 "package test;", 33 "", 34 "import dagger.Component;", 35 "", 36 "@Component", 37 "interface TestComponent {", 38 " ChildComponent newChildComponent();", 39 "}"); 40 JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 41 "package test;", 42 "", 43 "import dagger.Subcomponent;", 44 "", 45 "@Subcomponent(modules = ModuleWithParameters.class)", 46 "interface ChildComponent {", 47 " Object object();", 48 "}"); 49 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ModuleWithParameters", 50 "package test;", 51 "", 52 "import dagger.Module;", 53 "import dagger.Provides;", 54 "", 55 "@Module", 56 "final class ModuleWithParameters {", 57 " private final Object object;", 58 "", 59 " ModuleWithParameters(Object object) {", 60 " this.object = object;", 61 " }", 62 "", 63 " @Provides Object object() {", 64 " return object;", 65 " }", 66 "}"); 67 assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile, moduleFile)) 68 .processedWith(new ComponentProcessor()) 69 .failsToCompile() 70 .withErrorContaining( 71 "test.ChildComponent requires modules which have no visible default constructors. " 72 + "Add the following modules as parameters to this method: " 73 + "test.ModuleWithParameters") 74 .in(componentFile).onLine(7); 75 } 76 factoryMethod_nonModuleParameter()77 @Test public void factoryMethod_nonModuleParameter() { 78 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 79 "package test;", 80 "", 81 "import dagger.Component;", 82 "", 83 "@Component", 84 "interface TestComponent {", 85 " ChildComponent newChildComponent(String someRandomString);", 86 "}"); 87 JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 88 "package test;", 89 "", 90 "import dagger.Subcomponent;", 91 "", 92 "@Subcomponent", 93 "interface ChildComponent {}"); 94 assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile)) 95 .processedWith(new ComponentProcessor()) 96 .failsToCompile() 97 .withErrorContaining( 98 "Subcomponent factory methods may only accept modules, but java.lang.String is not.") 99 .in(componentFile).onLine(7).atColumn(43); 100 } 101 factoryMethod_duplicateParameter()102 @Test public void factoryMethod_duplicateParameter() { 103 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 104 "package test;", 105 "", 106 "import dagger.Module;", 107 "", 108 "@Module", 109 "final class TestModule {}"); 110 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 111 "package test;", 112 "", 113 "import dagger.Component;", 114 "", 115 "@Component", 116 "interface TestComponent {", 117 " ChildComponent newChildComponent(TestModule testModule1, TestModule testModule2);", 118 "}"); 119 JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 120 "package test;", 121 "", 122 "import dagger.Subcomponent;", 123 "", 124 "@Subcomponent(modules = TestModule.class)", 125 "interface ChildComponent {}"); 126 assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) 127 .processedWith(new ComponentProcessor()) 128 .failsToCompile() 129 .withErrorContaining( 130 "A module may only occur once an an argument in a Subcomponent factory method, " 131 + "but test.TestModule was already passed.") 132 .in(componentFile).onLine(7).atColumn(71); 133 } 134 factoryMethod_superflouousModule()135 @Test public void factoryMethod_superflouousModule() { 136 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 137 "package test;", 138 "", 139 "import dagger.Module;", 140 "", 141 "@Module", 142 "final class TestModule {}"); 143 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 144 "package test;", 145 "", 146 "import dagger.Component;", 147 "", 148 "@Component", 149 "interface TestComponent {", 150 " ChildComponent newChildComponent(TestModule testModule);", 151 "}"); 152 JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 153 "package test;", 154 "", 155 "import dagger.Subcomponent;", 156 "", 157 "@Subcomponent", 158 "interface ChildComponent {}"); 159 assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) 160 .processedWith(new ComponentProcessor()) 161 .failsToCompile() 162 .withErrorContaining( 163 "test.TestModule is present as an argument to the test.ChildComponent factory method, but " 164 + "is not one of the modules used to implement the subcomponent.") 165 .in(componentFile).onLine(7); 166 } 167 missingBinding()168 @Test public void missingBinding() { 169 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 170 "package test;", 171 "", 172 "import dagger.Module;", 173 "import dagger.Provides;", 174 "", 175 "@Module", 176 "final class TestModule {", 177 " @Provides String provideString(int i) {", 178 " return Integer.toString(i);", 179 " }", 180 "}"); 181 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 182 "package test;", 183 "", 184 "import dagger.Component;", 185 "", 186 "@Component", 187 "interface TestComponent {", 188 " ChildComponent newChildComponent();", 189 "}"); 190 JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 191 "package test;", 192 "", 193 "import dagger.Subcomponent;", 194 "", 195 "@Subcomponent(modules = TestModule.class)", 196 "interface ChildComponent {", 197 " String getString();", 198 "}"); 199 assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) 200 .processedWith(new ComponentProcessor()) 201 .failsToCompile() 202 .withErrorContaining( 203 "java.lang.Integer cannot be provided without an @Inject constructor or from an " 204 + "@Provides-annotated method"); 205 } 206 subcomponentOnConcreteType()207 @Test public void subcomponentOnConcreteType() { 208 JavaFileObject subcomponentFile = JavaFileObjects.forSourceLines("test.NotASubcomponent", 209 "package test;", 210 "", 211 "import dagger.Subcomponent;", 212 "", 213 "@Subcomponent", 214 "final class NotASubcomponent {}"); 215 assertAbout(javaSources()).that(ImmutableList.of(subcomponentFile)) 216 .processedWith(new ComponentProcessor()) 217 .failsToCompile() 218 .withErrorContaining("interface"); 219 } 220 scopeMismatch()221 @Test public void scopeMismatch() { 222 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent", 223 "package test;", 224 "", 225 "import dagger.Component;", 226 "import javax.inject.Singleton;", 227 "", 228 "@Component", 229 "@Singleton", 230 "interface ParentComponent {", 231 " ChildComponent childComponent();", 232 "}"); 233 JavaFileObject subcomponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", 234 "package test;", 235 "", 236 "import dagger.Subcomponent;", 237 "", 238 "@Subcomponent(modules = ChildModule.class)", 239 "interface ChildComponent {", 240 " Object getObject();", 241 "}"); 242 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ChildModule", 243 "package test;", 244 "", 245 "import dagger.Module;", 246 "import dagger.Provides;", 247 "import javax.inject.Singleton;", 248 "", 249 "@Module", 250 "final class ChildModule {", 251 " @Provides @Singleton Object provideObject() { return null; }", 252 "}"); 253 assertAbout(javaSources()).that(ImmutableList.of(componentFile, subcomponentFile, moduleFile)) 254 .processedWith(new ComponentProcessor()) 255 .failsToCompile() 256 .withErrorContaining("@Singleton"); 257 } 258 259 @Test delegateFactoryNotCreatedForSubcomponentWhenProviderExistsInParent()260 public void delegateFactoryNotCreatedForSubcomponentWhenProviderExistsInParent() { 261 JavaFileObject parentComponentFile = 262 JavaFileObjects.forSourceLines( 263 "test.ParentComponent", 264 "package test;", 265 "", 266 "import dagger.Component;", 267 "", 268 "@Component", 269 "interface ParentComponent {", 270 " ChildComponent childComponent();", 271 " Dep1 getDep1();", 272 " Dep2 getDep2();", 273 "}"); 274 JavaFileObject childComponentFile = 275 JavaFileObjects.forSourceLines( 276 "test.ChildComponent", 277 "package test;", 278 "", 279 "import dagger.Subcomponent;", 280 "", 281 "@Subcomponent(modules = ChildModule.class)", 282 "interface ChildComponent {", 283 " Object getObject();", 284 "}"); 285 JavaFileObject childModuleFile = 286 JavaFileObjects.forSourceLines( 287 "test.ChildModule", 288 "package test;", 289 "", 290 "import dagger.Module;", 291 "import dagger.Provides;", 292 "", 293 "@Module", 294 "final class ChildModule {", 295 " @Provides Object provideObject(A a) { return null; }", 296 "}"); 297 JavaFileObject aFile = 298 JavaFileObjects.forSourceLines( 299 "test.A", 300 "package test;", 301 "", 302 "import javax.inject.Inject;", 303 "", 304 "final class A {", 305 " @Inject public A(NeedsDep1 a, Dep1 b, Dep2 c) { }", 306 " @Inject public void methodA() { }", 307 "}"); 308 JavaFileObject needsDep1File = 309 JavaFileObjects.forSourceLines( 310 "test.NeedsDep1", 311 "package test;", 312 "", 313 "import javax.inject.Inject;", 314 "", 315 "final class NeedsDep1 {", 316 " @Inject public NeedsDep1(Dep1 d) { }", 317 "}"); 318 JavaFileObject dep1File = 319 JavaFileObjects.forSourceLines( 320 "test.Dep1", 321 "package test;", 322 "", 323 "import javax.inject.Inject;", 324 "", 325 "final class Dep1 {", 326 " @Inject public Dep1() { }", 327 " @Inject public void dep1Method() { }", 328 "}"); 329 JavaFileObject dep2File = 330 JavaFileObjects.forSourceLines( 331 "test.Dep2", 332 "package test;", 333 "", 334 "import javax.inject.Inject;", 335 "", 336 "final class Dep2 {", 337 " @Inject public Dep2() { }", 338 " @Inject public void dep2Method() { }", 339 "}"); 340 341 JavaFileObject componentGeneratedFile = 342 JavaFileObjects.forSourceLines( 343 "DaggerParentComponent", 344 "package test;", 345 "", 346 "import dagger.MembersInjector;", 347 "import javax.annotation.Generated;", 348 "import javax.inject.Provider;", 349 "", 350 "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", 351 "public final class DaggerParentComponent implements ParentComponent {", 352 " private MembersInjector<Dep1> dep1MembersInjector;", 353 " private Provider<Dep1> dep1Provider;", 354 " private MembersInjector<Dep2> dep2MembersInjector;", 355 " private Provider<Dep2> dep2Provider;", 356 "", 357 " private DaggerParentComponent(Builder builder) { ", 358 " assert builder != null;", 359 " initialize(builder);", 360 " }", 361 "", 362 " public static Builder builder() { ", 363 " return new Builder();", 364 " }", 365 "", 366 " public static ParentComponent create() { ", 367 " return builder().build();", 368 " }", 369 "", 370 " @SuppressWarnings(\"unchecked\")", 371 " private void initialize(final Builder builder) { ", 372 " this.dep1MembersInjector = Dep1_MembersInjector.create();", 373 " this.dep1Provider = Dep1_Factory.create(dep1MembersInjector);", 374 " this.dep2MembersInjector = Dep2_MembersInjector.create();", 375 " this.dep2Provider = Dep2_Factory.create(dep2MembersInjector);", 376 " }", 377 "", 378 " @Override", 379 " public Dep1 getDep1() { ", 380 " return dep1Provider.get();", 381 " }", 382 "", 383 " @Override", 384 " public Dep2 getDep2() { ", 385 " return dep2Provider.get();", 386 " }", 387 "", 388 " @Override", 389 " public ChildComponent childComponent() { ", 390 " return new ChildComponentImpl();", 391 " }", 392 "", 393 " public static final class Builder {", 394 " private Builder() { ", 395 " }", 396 " ", 397 " public ParentComponent build() { ", 398 " return new DaggerParentComponent(this);", 399 " }", 400 " }", 401 "", 402 " private final class ChildComponentImpl implements ChildComponent {", 403 " private final ChildModule childModule;", 404 " private MembersInjector<A> aMembersInjector;", 405 " private Provider<NeedsDep1> needsDep1Provider;", 406 " private Provider<A> aProvider;", 407 " private Provider<Object> provideObjectProvider;", 408 " ", 409 " private ChildComponentImpl() { ", 410 " this.childModule = new ChildModule();", 411 " initialize();", 412 " }", 413 "", 414 " @SuppressWarnings(\"unchecked\")", 415 " private void initialize() { ", 416 " this.aMembersInjector = A_MembersInjector.create();", 417 " this.needsDep1Provider = NeedsDep1_Factory.create(", 418 " DaggerParentComponent.this.dep1Provider);", 419 " this.aProvider = A_Factory.create(", 420 " aMembersInjector,", 421 " needsDep1Provider,", 422 " DaggerParentComponent.this.dep1Provider,", 423 " DaggerParentComponent.this.dep2Provider);", 424 " this.provideObjectProvider = ChildModule_ProvideObjectFactory.create(", 425 " childModule, aProvider);", 426 " }", 427 " ", 428 " @Override", 429 " public Object getObject() { ", 430 " return provideObjectProvider.get();", 431 " }", 432 " }", 433 "}"); 434 assertAbout(javaSources()) 435 .that( 436 ImmutableList.of( 437 parentComponentFile, 438 childComponentFile, 439 childModuleFile, 440 aFile, 441 needsDep1File, 442 dep1File, 443 dep2File)) 444 .processedWith(new ComponentProcessor()) 445 .compilesWithoutError() 446 .and() 447 .generatesSources(componentGeneratedFile); 448 } 449 } 450