1 /* 2 * Copyright (C) 2014 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen; 18 19 import androidx.room.compiler.processing.util.Source; 20 import com.google.common.collect.ImmutableMap; 21 import dagger.testing.compile.CompilerTests; 22 import dagger.testing.golden.GoldenFileRule; 23 import java.util.Collection; 24 import org.junit.Rule; 25 import org.junit.Test; 26 import org.junit.runner.RunWith; 27 import org.junit.runners.Parameterized; 28 import org.junit.runners.Parameterized.Parameters; 29 30 @RunWith(Parameterized.class) 31 public class ProductionComponentProcessorTest { 32 @Parameters(name = "{0}") parameters()33 public static Collection<Object[]> parameters() { 34 return CompilerMode.TEST_PARAMETERS; 35 } 36 37 @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule(); 38 39 private final CompilerMode compilerMode; 40 ProductionComponentProcessorTest(CompilerMode compilerMode)41 public ProductionComponentProcessorTest(CompilerMode compilerMode) { 42 this.compilerMode = compilerMode; 43 } 44 componentOnConcreteClass()45 @Test public void componentOnConcreteClass() { 46 Source componentFile = 47 CompilerTests.javaSource("test.NotAComponent", 48 "package test;", 49 "", 50 "import dagger.producers.ProductionComponent;", 51 "", 52 "@ProductionComponent", 53 "final class NotAComponent {}"); 54 CompilerTests.daggerCompiler(componentFile) 55 .withProcessingOptions(compilerMode.processorOptions()) 56 .compile( 57 subject -> { 58 subject.hasErrorCount(1); 59 subject.hasErrorContaining( 60 "@ProductionComponent may only be applied to an interface or abstract class"); 61 }); 62 } 63 componentOnEnum()64 @Test public void componentOnEnum() { 65 Source componentFile = 66 CompilerTests.javaSource("test.NotAComponent", 67 "package test;", 68 "", 69 "import dagger.producers.ProductionComponent;", 70 "", 71 "@ProductionComponent", 72 "enum NotAComponent {", 73 " INSTANCE", 74 "}"); 75 CompilerTests.daggerCompiler(componentFile) 76 .withProcessingOptions(compilerMode.processorOptions()) 77 .compile( 78 subject -> { 79 subject.hasErrorCount(1); 80 subject.hasErrorContaining( 81 "@ProductionComponent may only be applied to an interface or abstract class"); 82 }); 83 } 84 componentOnAnnotation()85 @Test public void componentOnAnnotation() { 86 Source componentFile = 87 CompilerTests.javaSource("test.NotAComponent", 88 "package test;", 89 "", 90 "import dagger.producers.ProductionComponent;", 91 "", 92 "@ProductionComponent", 93 "@interface NotAComponent {}"); 94 CompilerTests.daggerCompiler(componentFile) 95 .withProcessingOptions(compilerMode.processorOptions()) 96 .compile( 97 subject -> { 98 subject.hasErrorCount(1); 99 subject.hasErrorContaining( 100 "@ProductionComponent may only be applied to an interface or abstract class"); 101 }); 102 } 103 nonModuleModule()104 @Test public void nonModuleModule() { 105 Source componentFile = 106 CompilerTests.javaSource("test.NotAComponent", 107 "package test;", 108 "", 109 "import dagger.producers.ProductionComponent;", 110 "", 111 "@ProductionComponent(modules = Object.class)", 112 "interface NotAComponent {}"); 113 CompilerTests.daggerCompiler(componentFile) 114 .withProcessingOptions(compilerMode.processorOptions()) 115 .compile( 116 subject -> { 117 subject.hasErrorCount(1); 118 subject.hasErrorContaining("is not annotated with one of @Module, @ProducerModule"); 119 }); 120 } 121 122 @Test dependsOnProductionExecutor()123 public void dependsOnProductionExecutor() throws Exception { 124 Source moduleFile = 125 CompilerTests.javaSource( 126 "test.ExecutorModule", 127 "package test;", 128 "", 129 "import com.google.common.util.concurrent.MoreExecutors;", 130 "import dagger.Module;", 131 "import dagger.Provides;", 132 "import dagger.producers.Production;", 133 "import java.util.concurrent.Executor;", 134 "", 135 "@Module", 136 "final class ExecutorModule {", 137 " @Provides @Production Executor executor() {", 138 " return MoreExecutors.directExecutor();", 139 " }", 140 "}"); 141 Source producerModuleFile = 142 CompilerTests.javaSource( 143 "test.SimpleModule", 144 "package test;", 145 "", 146 "import dagger.producers.ProducerModule;", 147 "import dagger.producers.Produces;", 148 "import dagger.producers.Production;", 149 "import java.util.concurrent.Executor;", 150 "", 151 "@ProducerModule", 152 "final class SimpleModule {", 153 " @Produces String str(@Production Executor executor) {", 154 " return \"\";", 155 " }", 156 "}"); 157 Source componentFile = 158 CompilerTests.javaSource( 159 "test.SimpleComponent", 160 "package test;", 161 "", 162 "import com.google.common.util.concurrent.ListenableFuture;", 163 "import dagger.producers.ProductionComponent;", 164 "import java.util.concurrent.Executor;", 165 "", 166 "@ProductionComponent(modules = {ExecutorModule.class, SimpleModule.class})", 167 "interface SimpleComponent {", 168 " ListenableFuture<String> str();", 169 "", 170 " @ProductionComponent.Builder", 171 " interface Builder {", 172 " SimpleComponent build();", 173 " }", 174 "}"); 175 176 String errorMessage = "String may not depend on the production executor"; 177 CompilerTests.daggerCompiler(moduleFile, producerModuleFile, componentFile) 178 .withProcessingOptions(compilerMode.processorOptions()) 179 .compile( 180 subject -> { 181 subject.hasErrorCount(1); 182 subject.hasErrorContaining(errorMessage) 183 .onSource(componentFile) 184 .onLineContaining("interface SimpleComponent"); 185 }); 186 187 // Verify that the error is reported on the module when fullBindingGraphValidation is enabled. 188 CompilerTests.daggerCompiler(producerModuleFile) 189 .withProcessingOptions( 190 ImmutableMap.<String, String>builder() 191 .putAll(compilerMode.processorOptions()) 192 .put("dagger.fullBindingGraphValidation", "ERROR") 193 .buildOrThrow()) 194 .compile( 195 subject -> { 196 subject.hasErrorCount(1); 197 subject.hasErrorContaining(errorMessage) 198 .onSource(producerModuleFile) 199 .onLineContaining("class SimpleModule"); 200 }); 201 // TODO(dpb): Report at the binding if enclosed in the module. 202 } 203 204 @Test dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs()205 public void dependsOnProductionSubcomponentWithPluginsVisitFullBindingGraphs() throws Exception { 206 Source myComponent = 207 CompilerTests.javaSource( 208 "test.MyComponent", 209 "package test;", 210 "", 211 "import dagger.Component;", 212 "", 213 "@Component(modules = MyModule.class)", 214 "interface MyComponent {}"); 215 Source myModule = 216 CompilerTests.javaSource( 217 "test.MyModule", 218 "package test;", 219 "", 220 "import dagger.Component;", 221 "import dagger.Module;", 222 "", 223 "@Module(subcomponents = MyProductionSubcomponent.class)", 224 "interface MyModule {}"); 225 Source myProductionSubcomponent = 226 CompilerTests.javaSource( 227 "test.MyProductionSubcomponent", 228 "package test;", 229 "", 230 "import dagger.producers.ProductionSubcomponent;", 231 "", 232 "@ProductionSubcomponent", 233 "interface MyProductionSubcomponent {", 234 " @ProductionSubcomponent.Builder", 235 " interface Builder {", 236 " MyProductionSubcomponent build();", 237 " }", 238 "}"); 239 240 CompilerTests.daggerCompiler(myComponent, myModule, myProductionSubcomponent) 241 .withProcessingOptions( 242 ImmutableMap.<String, String>builder() 243 .putAll(compilerMode.processorOptions()) 244 .put("dagger.pluginsVisitFullBindingGraphs", "ENABLED") 245 .buildOrThrow()) 246 .compile(subject -> subject.hasErrorCount(0)); 247 } 248 249 @Test simpleComponent()250 public void simpleComponent() throws Exception { 251 Source component = 252 CompilerTests.javaSource( 253 "test.TestClass", 254 "package test;", 255 "", 256 "import com.google.common.util.concurrent.ListenableFuture;", 257 "import com.google.common.util.concurrent.MoreExecutors;", 258 "import dagger.Module;", 259 "import dagger.Provides;", 260 "import dagger.producers.ProducerModule;", 261 "import dagger.producers.Produces;", 262 "import dagger.producers.Production;", 263 "import dagger.producers.ProductionComponent;", 264 "import java.util.concurrent.Executor;", 265 "import javax.inject.Inject;", 266 "", 267 "final class TestClass {", 268 " static final class C {", 269 " @Inject C() {}", 270 " }", 271 "", 272 " interface A {}", 273 " interface B {}", 274 "", 275 " @Module", 276 " static final class BModule {", 277 " @Provides B b(C c) {", 278 " return null;", 279 " }", 280 "", 281 " @Provides @Production Executor executor() {", 282 " return MoreExecutors.directExecutor();", 283 " }", 284 " }", 285 "", 286 " @ProducerModule", 287 " static final class AModule {", 288 " @Produces ListenableFuture<A> a(B b) {", 289 " return null;", 290 " }", 291 " }", 292 "", 293 " @ProductionComponent(modules = {AModule.class, BModule.class})", 294 " interface SimpleComponent {", 295 " ListenableFuture<A> a();", 296 " }", 297 "}"); 298 299 CompilerTests.daggerCompiler(component) 300 .withProcessingOptions(compilerMode.processorOptions()) 301 .compile( 302 subject -> { 303 subject.hasErrorCount(0); 304 subject.generatedSource( 305 goldenFileRule.goldenSource("test/DaggerTestClass_SimpleComponent")); 306 }); 307 } 308 nullableProducersAreNotErrors()309 @Test public void nullableProducersAreNotErrors() { 310 Source component = 311 CompilerTests.javaSource("test.TestClass", 312 "package test;", 313 "", 314 "import com.google.common.util.concurrent.ListenableFuture;", 315 "import com.google.common.util.concurrent.MoreExecutors;", 316 "import dagger.Module;", 317 "import dagger.Provides;", 318 "import dagger.producers.ProducerModule;", 319 "import dagger.producers.Produces;", 320 "import dagger.producers.Production;", 321 "import dagger.producers.ProductionComponent;", 322 "import java.util.concurrent.Executor;", 323 "import javax.annotation.Nullable;", 324 "import javax.inject.Inject;", 325 "", 326 "final class TestClass {", 327 " interface A {}", 328 " interface B {}", 329 " interface C {}", 330 "", 331 " @Module", 332 " static final class CModule {", 333 " @Provides @Nullable C c() {", 334 " return null;", 335 " }", 336 "", 337 " @Provides @Production Executor executor() {", 338 " return MoreExecutors.directExecutor();", 339 " }", 340 " }", 341 "", 342 " @ProducerModule", 343 " static final class ABModule {", 344 " @Produces @Nullable B b(@Nullable C c) {", 345 " return null;", 346 " }", 347 348 " @Produces @Nullable ListenableFuture<A> a(B b) {", // NOTE: B not injected as nullable 349 " return null;", 350 " }", 351 " }", 352 "", 353 " @ProductionComponent(modules = {ABModule.class, CModule.class})", 354 " interface SimpleComponent {", 355 " ListenableFuture<A> a();", 356 " }", 357 "}"); 358 CompilerTests.daggerCompiler(component) 359 .withProcessingOptions(compilerMode.processorOptions()) 360 .compile( 361 subject -> { 362 subject.hasErrorCount(0); 363 subject.hasWarningCount(2); 364 subject.hasWarningContaining("@Nullable on @Produces methods does not do anything") 365 .onSource(component) 366 .onLine(33); 367 subject.hasWarningContaining("@Nullable on @Produces methods does not do anything") 368 .onSource(component) 369 .onLine(36); 370 }); 371 } 372 373 @Test productionScope_injectConstructor()374 public void productionScope_injectConstructor() throws Exception { 375 Source productionScoped = 376 CompilerTests.javaSource( 377 "test.ProductionScoped", 378 "package test;", 379 "", 380 "import dagger.producers.ProductionScope;", 381 "import javax.inject.Inject;", 382 "", 383 "@ProductionScope", 384 "class ProductionScoped {", 385 " @Inject ProductionScoped() {}", 386 "}"); 387 Source parent = 388 CompilerTests.javaSource( 389 "test.Parent", 390 "package test;", 391 "", 392 "import dagger.producers.ProductionComponent;", 393 "", 394 "@ProductionComponent", 395 "interface Parent {", 396 " Child child();", 397 "}"); 398 Source child = 399 CompilerTests.javaSource( 400 "test.Child", 401 "package test;", 402 "", 403 "import dagger.producers.ProductionSubcomponent;", 404 "", 405 "@ProductionSubcomponent", 406 "interface Child {", 407 " ProductionScoped productionScoped();", 408 "}"); 409 410 CompilerTests.daggerCompiler(productionScoped, parent, child) 411 .withProcessingOptions(compilerMode.processorOptions()) 412 .compile( 413 subject -> { 414 subject.hasErrorCount(0); 415 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerParent")); 416 }); 417 } 418 } 419