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 // TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest. 18 package dagger.internal.codegen; 19 20 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; 21 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod; 22 import static java.lang.annotation.RetentionPolicy.RUNTIME; 23 24 import androidx.room.compiler.processing.util.Source; 25 import com.google.common.collect.ImmutableMap; 26 import com.google.common.util.concurrent.ListenableFuture; 27 import dagger.testing.compile.CompilerTests; 28 import dagger.testing.golden.GoldenFileRule; 29 import java.lang.annotation.Retention; 30 import javax.inject.Qualifier; 31 import org.junit.Rule; 32 import org.junit.Test; 33 import org.junit.runner.RunWith; 34 import org.junit.runners.JUnit4; 35 36 @RunWith(JUnit4.class) 37 public class ProducerModuleFactoryGeneratorTest { 38 39 @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule(); 40 producesMethodNotInModule()41 @Test public void producesMethodNotInModule() { 42 assertThatMethodInUnannotatedClass("@Produces String produceString() { return null; }") 43 .hasError("@Produces methods can only be present within a @ProducerModule"); 44 } 45 producesMethodAbstract()46 @Test public void producesMethodAbstract() { 47 assertThatProductionModuleMethod("@Produces abstract String produceString();") 48 .hasError("@Produces methods cannot be abstract"); 49 } 50 producesMethodPrivate()51 @Test public void producesMethodPrivate() { 52 assertThatProductionModuleMethod("@Produces private String produceString() { return null; }") 53 .hasError("@Produces methods cannot be private"); 54 } 55 producesMethodReturnVoid()56 @Test public void producesMethodReturnVoid() { 57 assertThatProductionModuleMethod("@Produces void produceNothing() {}") 58 .hasError("@Produces methods must return a value (not void)"); 59 } 60 61 @Test producesProvider()62 public void producesProvider() { 63 assertThatProductionModuleMethod("@Produces Provider<String> produceProvider() {}") 64 .hasError("@Produces methods must not return framework types"); 65 } 66 67 @Test producesLazy()68 public void producesLazy() { 69 assertThatProductionModuleMethod("@Produces Lazy<String> produceLazy() {}") 70 .hasError("@Produces methods must not return framework types"); 71 } 72 73 @Test producesMembersInjector()74 public void producesMembersInjector() { 75 assertThatProductionModuleMethod( 76 "@Produces MembersInjector<String> produceMembersInjector() {}") 77 .hasError("@Produces methods must not return framework types"); 78 } 79 80 @Test producesProducer()81 public void producesProducer() { 82 assertThatProductionModuleMethod("@Produces Producer<String> produceProducer() {}") 83 .hasError("@Produces methods must not return framework types"); 84 } 85 86 @Test producesProduced()87 public void producesProduced() { 88 assertThatProductionModuleMethod("@Produces Produced<String> produceProduced() {}") 89 .hasError("@Produces methods must not return framework types"); 90 } 91 producesMethodReturnRawFuture()92 @Test public void producesMethodReturnRawFuture() { 93 assertThatProductionModuleMethod("@Produces ListenableFuture produceRaw() {}") 94 .importing(ListenableFuture.class) 95 .hasError("@Produces methods cannot return a raw ListenableFuture"); 96 } 97 producesMethodReturnWildcardFuture()98 @Test public void producesMethodReturnWildcardFuture() { 99 assertThatProductionModuleMethod("@Produces ListenableFuture<?> produceRaw() {}") 100 .importing(ListenableFuture.class) 101 .hasError( 102 "@Produces methods can return only a primitive, an array, a type variable, " 103 + "a declared type, or a ListenableFuture of one of those types"); 104 } 105 producesMethodWithTypeParameter()106 @Test public void producesMethodWithTypeParameter() { 107 assertThatProductionModuleMethod("@Produces <T> String produceString() { return null; }") 108 .hasError("@Produces methods may not have type parameters"); 109 } 110 producesMethodSetValuesWildcard()111 @Test public void producesMethodSetValuesWildcard() { 112 assertThatProductionModuleMethod( 113 "@Produces @ElementsIntoSet Set<?> produceWildcard() { return null; }") 114 .hasError( 115 "@Produces methods can return only a primitive, an array, a type variable, " 116 + "a declared type, or a ListenableFuture of one of those types"); 117 } 118 producesMethodSetValuesRawSet()119 @Test public void producesMethodSetValuesRawSet() { 120 assertThatProductionModuleMethod( 121 "@Produces @ElementsIntoSet Set produceSomething() { return null; }") 122 .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set"); 123 } 124 producesMethodSetValuesNotASet()125 @Test public void producesMethodSetValuesNotASet() { 126 assertThatProductionModuleMethod( 127 "@Produces @ElementsIntoSet List<String> produceStrings() { return null; }") 128 .hasError( 129 "@Produces methods of type set values must return a Set or ListenableFuture of Set"); 130 } 131 producesMethodSetValuesWildcardInFuture()132 @Test public void producesMethodSetValuesWildcardInFuture() { 133 assertThatProductionModuleMethod( 134 "@Produces @ElementsIntoSet " 135 + "ListenableFuture<Set<?>> produceWildcard() { return null; }") 136 .importing(ListenableFuture.class) 137 .hasError( 138 "@Produces methods can return only a primitive, an array, a type variable, " 139 + "a declared type, or a ListenableFuture of one of those types"); 140 } 141 producesMethodSetValuesFutureRawSet()142 @Test public void producesMethodSetValuesFutureRawSet() { 143 assertThatProductionModuleMethod( 144 "@Produces @ElementsIntoSet ListenableFuture<Set> produceSomething() { return null; }") 145 .importing(ListenableFuture.class) 146 .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set"); 147 } 148 producesMethodSetValuesFutureNotASet()149 @Test public void producesMethodSetValuesFutureNotASet() { 150 assertThatProductionModuleMethod( 151 "@Produces @ElementsIntoSet " 152 + "ListenableFuture<List<String>> produceStrings() { return null; }") 153 .importing(ListenableFuture.class) 154 .hasError( 155 "@Produces methods of type set values must return a Set or ListenableFuture of Set"); 156 } 157 multipleProducesMethodsWithSameName()158 @Test public void multipleProducesMethodsWithSameName() { 159 Source moduleFile = 160 CompilerTests.javaSource( 161 "test.TestModule", 162 "package test;", 163 "", 164 "import dagger.producers.ProducerModule;", 165 "import dagger.producers.Produces;", 166 "", 167 "@ProducerModule", 168 "final class TestModule {", 169 " @Produces Object produce(int i) {", 170 " return i;", 171 " }", 172 "", 173 " @Produces String produce() {", 174 " return \"\";", 175 " }", 176 "}"); 177 String errorMessage = 178 "Cannot have more than one binding method with the same name in a single module"; 179 CompilerTests.daggerCompiler(moduleFile) 180 .compile( 181 subject -> { 182 subject.hasErrorCount(2); 183 subject.hasErrorContaining(errorMessage).onSource(moduleFile).onLine(8); 184 subject.hasErrorContaining(errorMessage).onSource(moduleFile).onLine(12); 185 }); 186 } 187 188 @Test producesMethodThrowsThrowable()189 public void producesMethodThrowsThrowable() { 190 assertThatProductionModuleMethod("@Produces int produceInt() throws Throwable { return 0; }") 191 .hasError( 192 "@Produces methods may only throw unchecked exceptions or exceptions subclassing " 193 + "Exception"); 194 } 195 producesMethodWithScope()196 @Test public void producesMethodWithScope() { 197 assertThatProductionModuleMethod("@Produces @Singleton String str() { return \"\"; }") 198 .hasError("@Produces methods cannot be scoped"); 199 } 200 201 @Test privateModule()202 public void privateModule() { 203 Source moduleFile = 204 CompilerTests.javaSource("test.Enclosing", 205 "package test;", 206 "", 207 "import dagger.producers.ProducerModule;", 208 "", 209 "final class Enclosing {", 210 " @ProducerModule private static final class PrivateModule {", 211 " }", 212 "}"); 213 CompilerTests.daggerCompiler(moduleFile) 214 .compile( 215 subject -> { 216 subject.hasErrorCount(1); 217 subject.hasErrorContaining("Modules cannot be private") 218 .onSource(moduleFile) 219 .onLine(6); 220 }); 221 } 222 223 224 @Test enclosedInPrivateModule()225 public void enclosedInPrivateModule() { 226 Source moduleFile = 227 CompilerTests.javaSource( 228 "test.Enclosing", 229 "package test;", 230 "", 231 "import dagger.producers.ProducerModule;", 232 "", 233 "final class Enclosing {", 234 " private static final class PrivateEnclosing {", 235 " @ProducerModule static final class TestModule {", 236 " }", 237 " }", 238 "}"); 239 CompilerTests.daggerCompiler(moduleFile) 240 .compile( 241 subject -> { 242 subject.hasErrorCount(1); 243 subject.hasErrorContaining("Modules cannot be enclosed in private types") 244 .onSource(moduleFile) 245 .onLine(7); 246 }); 247 } 248 249 @Test includesNonModule()250 public void includesNonModule() { 251 Source xFile = 252 CompilerTests.javaSource( 253 "test.X", 254 "package test;", 255 "", 256 "public final class X {}"); 257 Source moduleFile = 258 CompilerTests.javaSource( 259 "test.FooModule", 260 "package test;", 261 "", 262 "import dagger.producers.ProducerModule;", 263 "", 264 "@ProducerModule(includes = X.class)", 265 "public final class FooModule {", 266 "}"); 267 CompilerTests.daggerCompiler(xFile, moduleFile) 268 .compile( 269 subject -> { 270 subject.hasErrorCount(1); 271 subject.hasErrorContaining( 272 "X is listed as a module, but is not annotated with one of @Module, " 273 + "@ProducerModule"); 274 }); 275 } 276 277 // TODO(ronshapiro): merge this with the equivalent test in ModuleFactoryGeneratorTest and make it 278 // parameterized 279 @Test publicModuleNonPublicIncludes()280 public void publicModuleNonPublicIncludes() { 281 Source publicModuleFile = 282 CompilerTests.javaSource( 283 "test.PublicModule", 284 "package test;", 285 "", 286 "import dagger.producers.ProducerModule;", 287 "", 288 "@ProducerModule(includes = {", 289 " BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class", 290 "})", 291 "public final class PublicModule {}"); 292 Source badNonPublicModuleFile = 293 CompilerTests.javaSource( 294 "test.BadNonPublicModule", 295 "package test;", 296 "", 297 "import dagger.producers.ProducerModule;", 298 "import dagger.producers.Produces;", 299 "", 300 "@ProducerModule", 301 "final class BadNonPublicModule {", 302 " @Produces", 303 " int produceInt() {", 304 " return 42;", 305 " }", 306 "}"); 307 Source okNonPublicModuleFile = 308 CompilerTests.javaSource( 309 "test.OkNonPublicModule", 310 "package test;", 311 "", 312 "import dagger.producers.ProducerModule;", 313 "import dagger.producers.Produces;", 314 "", 315 "@ProducerModule", 316 "final class OkNonPublicModule {", 317 " @Produces", 318 " static String produceString() {", 319 " return \"foo\";", 320 " }", 321 "}"); 322 Source otherPublicModuleFile = 323 CompilerTests.javaSource( 324 "test.OtherPublicModule", 325 "package test;", 326 "", 327 "import dagger.producers.ProducerModule;", 328 "", 329 "@ProducerModule", 330 "public final class OtherPublicModule {", 331 "}"); 332 CompilerTests.daggerCompiler( 333 publicModuleFile, 334 badNonPublicModuleFile, 335 okNonPublicModuleFile, 336 otherPublicModuleFile) 337 .compile( 338 subject -> { 339 subject.hasErrorCount(1); 340 subject.hasErrorContaining( 341 "This module is public, but it includes non-public (or effectively non-public) " 342 + "modules (test.BadNonPublicModule) that have non-static, non-abstract " 343 + "binding methods. Either reduce the visibility of this module, make the " 344 + "included modules public, or make all of the binding methods on the " 345 + "included modules abstract or static.") 346 .onSource(publicModuleFile) 347 .onLine(8); 348 }); 349 } 350 argumentNamedModuleCompiles()351 @Test public void argumentNamedModuleCompiles() { 352 Source moduleFile = 353 CompilerTests.javaSource( 354 "test.TestModule", 355 "package test;", 356 "", 357 "import dagger.producers.ProducerModule;", 358 "import dagger.producers.Produces;", 359 "", 360 "@ProducerModule", 361 "final class TestModule {", 362 " @Produces String produceString(int module) {", 363 " return null;", 364 " }", 365 "}"); 366 CompilerTests.daggerCompiler(moduleFile) 367 .compile(subject -> subject.hasErrorCount(0)); 368 } 369 singleProducesMethodNoArgsFuture()370 @Test public void singleProducesMethodNoArgsFuture() { 371 Source moduleFile = 372 CompilerTests.javaSource( 373 "test.TestModule", 374 "package test;", 375 "", 376 "import com.google.common.util.concurrent.ListenableFuture;", 377 "import dagger.producers.ProducerModule;", 378 "import dagger.producers.Produces;", 379 "", 380 "@ProducerModule", 381 "final class TestModule {", 382 " @Produces ListenableFuture<String> produceString() {", 383 " return null;", 384 " }", 385 "}"); 386 CompilerTests.daggerCompiler(moduleFile) 387 .compile( 388 subject -> { 389 subject.hasErrorCount(0); 390 subject.generatedSource( 391 goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory")); 392 }); 393 } 394 395 @Test singleProducesMethodNoArgsFutureWithProducerName()396 public void singleProducesMethodNoArgsFutureWithProducerName() { 397 Source moduleFile = 398 CompilerTests.javaSource( 399 "test.TestModule", 400 "package test;", 401 "", 402 "import com.google.common.util.concurrent.Futures;", 403 "import com.google.common.util.concurrent.ListenableFuture;", 404 "import dagger.producers.ProducerModule;", 405 "import dagger.producers.Produces;", 406 "", 407 "@ProducerModule", 408 "final class TestModule {", 409 " @Produces ListenableFuture<String> produceString() {", 410 " return Futures.immediateFuture(\"\");", 411 " }", 412 "}"); 413 CompilerTests.daggerCompiler(moduleFile) 414 .withProcessingOptions(ImmutableMap.of("dagger.writeProducerNameInToken", "ENABLED")) 415 .compile( 416 subject -> { 417 subject.hasErrorCount(0); 418 subject.generatedSource( 419 goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory")); 420 }); 421 } 422 423 @Test producesMethodMultipleQualifiersOnMethod()424 public void producesMethodMultipleQualifiersOnMethod() { 425 assertThatProductionModuleMethod( 426 "@Produces @QualifierA @QualifierB static String produceString() { return null; }") 427 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 428 .hasError("may not use more than one @Qualifier"); 429 } 430 431 @Test producesMethodMultipleQualifiersOnParameter()432 public void producesMethodMultipleQualifiersOnParameter() { 433 assertThatProductionModuleMethod( 434 "@Produces static String produceString(@QualifierA @QualifierB Object input) " 435 + "{ return null; }") 436 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 437 .hasError("may not use more than one @Qualifier"); 438 } 439 440 @Test producesMethodWildcardDependency()441 public void producesMethodWildcardDependency() { 442 assertThatProductionModuleMethod( 443 "@Produces static String produceString(Provider<? extends Number> numberProvider) " 444 + "{ return null; }") 445 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 446 .hasError( 447 "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> " 448 + "when T is a wildcard type such as ? extends java.lang.Number"); 449 } 450 451 @Qualifier 452 @Retention(RUNTIME) 453 public @interface QualifierA {} 454 455 @Qualifier 456 @Retention(RUNTIME) 457 public @interface QualifierB {} 458 } 459