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 static com.google.testing.compile.CompilationSubject.assertThat; 20 import static dagger.internal.codegen.Compilers.compilerWithOptions; 21 import static dagger.internal.codegen.Compilers.daggerCompiler; 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 /** Producer-specific validation tests. */ 31 @RunWith(JUnit4.class) 32 public class ProductionGraphValidationTest { 33 private static final JavaFileObject EXECUTOR_MODULE = 34 JavaFileObjects.forSourceLines( 35 "test.ExecutorModule", 36 "package test;", 37 "", 38 "import com.google.common.util.concurrent.MoreExecutors;", 39 "import dagger.Module;", 40 "import dagger.Provides;", 41 "import dagger.producers.Production;", 42 "import java.util.concurrent.Executor;", 43 "", 44 "@Module", 45 "class ExecutorModule {", 46 " @Provides @Production Executor executor() {", 47 " return MoreExecutors.directExecutor();", 48 " }", 49 "}"); 50 componentWithUnprovidedInput()51 @Test public void componentWithUnprovidedInput() { 52 JavaFileObject component = JavaFileObjects.forSourceLines("test.MyComponent", 53 "package test;", 54 "", 55 "import com.google.common.util.concurrent.ListenableFuture;", 56 "import dagger.producers.ProductionComponent;", 57 "", 58 "@ProductionComponent(modules = {ExecutorModule.class, FooModule.class})", 59 "interface MyComponent {", 60 " ListenableFuture<Foo> getFoo();", 61 "}"); 62 JavaFileObject module = JavaFileObjects.forSourceLines("test.FooModule", 63 "package test;", 64 "", 65 "import dagger.producers.ProducerModule;", 66 "import dagger.producers.Produces;", 67 "", 68 "class Foo {}", 69 "class Bar {}", 70 "", 71 "@ProducerModule", 72 "class FooModule {", 73 " @Produces Foo foo(Bar bar) {", 74 " return null;", 75 " }", 76 "}"); 77 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, module, component); 78 assertThat(compilation).failed(); 79 assertThat(compilation) 80 .hadErrorContaining( 81 "Bar cannot be provided without an @Inject constructor or an @Provides- or " 82 + "@Produces-annotated method.") 83 .inFile(component) 84 .onLineContaining("interface MyComponent"); 85 } 86 componentProductionWithNoDependencyChain()87 @Test public void componentProductionWithNoDependencyChain() { 88 JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass", 89 "package test;", 90 "", 91 "import com.google.common.util.concurrent.ListenableFuture;", 92 "import dagger.producers.ProductionComponent;", 93 "", 94 "final class TestClass {", 95 " interface A {}", 96 "", 97 " @ProductionComponent(modules = ExecutorModule.class)", 98 " interface AComponent {", 99 " ListenableFuture<A> getA();", 100 " }", 101 "}"); 102 103 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 104 assertThat(compilation).failed(); 105 assertThat(compilation) 106 .hadErrorContaining( 107 "TestClass.A cannot be provided without an @Provides- or @Produces-annotated " 108 + "method.") 109 .inFile(component) 110 .onLineContaining("interface AComponent"); 111 } 112 provisionDependsOnProduction()113 @Test public void provisionDependsOnProduction() { 114 JavaFileObject component = 115 JavaFileObjects.forSourceLines( 116 "test.TestClass", 117 "package test;", 118 "", 119 "import com.google.common.util.concurrent.ListenableFuture;", 120 "import dagger.Provides;", 121 "import dagger.producers.ProducerModule;", 122 "import dagger.producers.Produces;", 123 "import dagger.producers.ProductionComponent;", 124 "", 125 "final class TestClass {", 126 " interface A {}", 127 " interface B {}", 128 "", 129 " @ProducerModule(includes = BModule.class)", 130 " final class AModule {", 131 " @Provides A a(B b) {", 132 " return null;", 133 " }", 134 " }", 135 "", 136 " @ProducerModule", 137 " final class BModule {", 138 " @Produces ListenableFuture<B> b() {", 139 " return null;", 140 " }", 141 " }", 142 "", 143 " @ProductionComponent(modules = {ExecutorModule.class, AModule.class})", 144 " interface AComponent {", 145 " ListenableFuture<A> getA();", 146 " }", 147 "}"); 148 149 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 150 assertThat(compilation).failed(); 151 assertThat(compilation) 152 .hadErrorContaining("TestClass.A is a provision, which cannot depend on a production.") 153 .inFile(component) 154 .onLineContaining("interface AComponent"); 155 156 compilation = 157 compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") 158 .compile(EXECUTOR_MODULE, component); 159 assertThat(compilation).failed(); 160 assertThat(compilation) 161 .hadErrorContaining("TestClass.A is a provision, which cannot depend on a production.") 162 .inFile(component) 163 .onLineContaining("class AModule"); 164 } 165 provisionEntryPointDependsOnProduction()166 @Test public void provisionEntryPointDependsOnProduction() { 167 JavaFileObject component = 168 JavaFileObjects.forSourceLines( 169 "test.TestClass", 170 "package test;", 171 "", 172 "import com.google.common.util.concurrent.ListenableFuture;", 173 "import dagger.producers.ProducerModule;", 174 "import dagger.producers.Produces;", 175 "import dagger.producers.ProductionComponent;", 176 "", 177 "final class TestClass {", 178 " interface A {}", 179 "", 180 " @ProducerModule", 181 " static final class AModule {", 182 " @Produces ListenableFuture<A> a() {", 183 " return null;", 184 " }", 185 " }", 186 "", 187 " @ProductionComponent(modules = {ExecutorModule.class, AModule.class})", 188 " interface AComponent {", 189 " A getA();", 190 " }", 191 "}"); 192 193 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 194 assertThat(compilation).failed(); 195 assertThat(compilation) 196 .hadErrorContaining( 197 "TestClass.A is a provision entry-point, which cannot depend on a production.") 198 .inFile(component) 199 .onLineContaining("interface AComponent"); 200 } 201 202 @Test providingMultibindingWithProductions()203 public void providingMultibindingWithProductions() { 204 JavaFileObject component = 205 JavaFileObjects.forSourceLines( 206 "test.TestClass", 207 "package test;", 208 "", 209 "import com.google.common.util.concurrent.ListenableFuture;", 210 "import dagger.Module;", 211 "import dagger.Provides;", 212 "import dagger.multibindings.IntoMap;", 213 "import dagger.multibindings.StringKey;", 214 "import dagger.producers.ProducerModule;", 215 "import dagger.producers.Produces;", 216 "import dagger.producers.ProductionComponent;", 217 "import java.util.Map;", 218 "import javax.inject.Provider;", 219 "", 220 "final class TestClass {", 221 " interface A {}", 222 " interface B {}", 223 "", 224 " @Module", 225 " static final class AModule {", 226 " @Provides static A a(Map<String, Provider<Object>> map) {", 227 " return null;", 228 " }", 229 "", 230 " @Provides @IntoMap @StringKey(\"a\") static Object aEntry() {", 231 " return \"a\";", 232 " }", 233 " }", 234 "", 235 " @ProducerModule", 236 " static final class BModule {", 237 " @Produces static B b(A a) {", 238 " return null;", 239 " }", 240 "", 241 " @Produces @IntoMap @StringKey(\"b\") static Object bEntry() {", 242 " return \"b\";", 243 " }", 244 " }", 245 "", 246 " @ProductionComponent(", 247 " modules = {ExecutorModule.class, AModule.class, BModule.class})", 248 " interface AComponent {", 249 " ListenableFuture<B> b();", 250 " }", 251 "}"); 252 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 253 assertThat(compilation).failed(); 254 assertThat(compilation) 255 .hadErrorContaining("TestClass.A is a provision, which cannot depend on a production") 256 .inFile(component) 257 .onLineContaining("interface AComponent"); 258 } 259 260 @Test monitoringDependsOnUnboundType()261 public void monitoringDependsOnUnboundType() { 262 JavaFileObject component = 263 JavaFileObjects.forSourceLines( 264 "test.TestClass", 265 "package test;", 266 "", 267 "import com.google.common.util.concurrent.ListenableFuture;", 268 "import dagger.Module;", 269 "import dagger.Provides;", 270 "import dagger.multibindings.IntoSet;", 271 "import dagger.producers.ProducerModule;", 272 "import dagger.producers.Produces;", 273 "import dagger.producers.ProductionComponent;", 274 "import dagger.producers.monitoring.ProductionComponentMonitor;", 275 "", 276 "final class TestClass {", 277 " interface A {}", 278 "", 279 " @Module", 280 " final class MonitoringModule {", 281 " @Provides @IntoSet", 282 " ProductionComponentMonitor.Factory monitorFactory(A unbound) {", 283 " return null;", 284 " }", 285 " }", 286 "", 287 " @ProducerModule", 288 " final class StringModule {", 289 " @Produces ListenableFuture<String> str() {", 290 " return null;", 291 " }", 292 " }", 293 "", 294 " @ProductionComponent(", 295 " modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}", 296 " )", 297 " interface StringComponent {", 298 " ListenableFuture<String> getString();", 299 " }", 300 "}"); 301 302 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 303 assertThat(compilation).failed(); 304 assertThat(compilation) 305 .hadErrorContaining( 306 "TestClass.A cannot be provided without an @Provides-annotated method.") 307 .inFile(component) 308 .onLineContaining("interface StringComponent"); 309 } 310 311 @Test monitoringDependsOnProduction()312 public void monitoringDependsOnProduction() { 313 JavaFileObject component = 314 JavaFileObjects.forSourceLines( 315 "test.TestClass", 316 "package test;", 317 "", 318 "import com.google.common.util.concurrent.ListenableFuture;", 319 "import dagger.Module;", 320 "import dagger.Provides;", 321 "import dagger.multibindings.IntoSet;", 322 "import dagger.producers.ProducerModule;", 323 "import dagger.producers.Produces;", 324 "import dagger.producers.ProductionComponent;", 325 "import dagger.producers.monitoring.ProductionComponentMonitor;", 326 "", 327 "final class TestClass {", 328 " interface A {}", 329 "", 330 " @Module", 331 " final class MonitoringModule {", 332 " @Provides @IntoSet ProductionComponentMonitor.Factory monitorFactory(A a) {", 333 " return null;", 334 " }", 335 " }", 336 "", 337 " @ProducerModule", 338 " final class StringModule {", 339 " @Produces A a() {", 340 " return null;", 341 " }", 342 "", 343 " @Produces ListenableFuture<String> str() {", 344 " return null;", 345 " }", 346 " }", 347 "", 348 " @ProductionComponent(", 349 " modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}", 350 " )", 351 " interface StringComponent {", 352 " ListenableFuture<String> getString();", 353 " }", 354 "}"); 355 356 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component); 357 assertThat(compilation).failed(); 358 assertThat(compilation) 359 .hadErrorContaining( 360 "Set<ProductionComponentMonitor.Factory>" 361 + " TestClass.MonitoringModule#monitorFactory is a provision," 362 + " which cannot depend on a production.") 363 .inFile(component) 364 .onLineContaining("interface StringComponent"); 365 } 366 367 @Test cycleNotBrokenByMap()368 public void cycleNotBrokenByMap() { 369 JavaFileObject component = 370 JavaFileObjects.forSourceLines( 371 "test.TestComponent", 372 "package test;", 373 "", 374 "import com.google.common.util.concurrent.ListenableFuture;", 375 "import dagger.producers.ProductionComponent;", 376 "", 377 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 378 "interface TestComponent {", 379 " ListenableFuture<String> string();", 380 "}"); 381 JavaFileObject module = 382 JavaFileObjects.forSourceLines( 383 "test.TestModule", 384 "package test;", 385 "", 386 "import dagger.producers.ProducerModule;", 387 "import dagger.producers.Produces;", 388 "import dagger.multibindings.IntoMap;", 389 "import dagger.multibindings.StringKey;", 390 "import java.util.Map;", 391 "", 392 "@ProducerModule", 393 "final class TestModule {", 394 " @Produces static String string(Map<String, String> map) {", 395 " return \"string\";", 396 " }", 397 "", 398 " @Produces @IntoMap @StringKey(\"key\")", 399 " static String entry(String string) {", 400 " return string;", 401 " }", 402 "}"); 403 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component, module); 404 assertThat(compilation).failed(); 405 assertThat(compilation) 406 .hadErrorContaining("cycle") 407 .inFile(component) 408 .onLineContaining("interface TestComponent"); 409 } 410 411 @Test cycleNotBrokenByProducerMap()412 public void cycleNotBrokenByProducerMap() { 413 JavaFileObject component = 414 JavaFileObjects.forSourceLines( 415 "test.TestComponent", 416 "package test;", 417 "", 418 "import com.google.common.util.concurrent.ListenableFuture;", 419 "import dagger.producers.ProductionComponent;", 420 "", 421 "@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})", 422 "interface TestComponent {", 423 " ListenableFuture<String> string();", 424 "}"); 425 JavaFileObject module = 426 JavaFileObjects.forSourceLines( 427 "test.TestModule", 428 "package test;", 429 "", 430 "import dagger.producers.Producer;", 431 "import dagger.producers.ProducerModule;", 432 "import dagger.producers.Produces;", 433 "import dagger.multibindings.StringKey;", 434 "import dagger.multibindings.IntoMap;", 435 "import java.util.Map;", 436 "", 437 "@ProducerModule", 438 "final class TestModule {", 439 " @Produces static String string(Map<String, Producer<String>> map) {", 440 " return \"string\";", 441 " }", 442 "", 443 " @Produces @IntoMap @StringKey(\"key\")", 444 " static String entry(String string) {", 445 " return string;", 446 " }", 447 "}"); 448 Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component, module); 449 assertThat(compilation).failed(); 450 assertThat(compilation) 451 .hadErrorContaining("cycle") 452 .inFile(component) 453 .onLineContaining("interface TestComponent"); 454 } 455 456 @Test componentWithBadModule()457 public void componentWithBadModule() { 458 JavaFileObject badModule = 459 JavaFileObjects.forSourceLines( 460 "test.BadModule", 461 "package test;", 462 "", 463 "import dagger.BindsOptionalOf;", 464 "import dagger.multibindings.Multibinds;", 465 "import dagger.Module;", 466 "import java.util.Set;", 467 "", 468 "@Module", 469 "abstract class BadModule {", 470 " @Multibinds", 471 " @BindsOptionalOf", 472 " abstract Set<String> strings();", 473 "}"); 474 JavaFileObject badComponent = 475 JavaFileObjects.forSourceLines( 476 "test.BadComponent", 477 "package test;", 478 "", 479 "import dagger.Component;", 480 "import java.util.Optional;", 481 "import java.util.Set;", 482 "", 483 "@Component(modules = BadModule.class)", 484 "interface BadComponent {", 485 " Set<String> strings();", 486 " Optional<Set<String>> optionalStrings();", 487 "}"); 488 Compilation compilation = daggerCompiler().compile(badModule, badComponent); 489 assertThat(compilation).failed(); 490 assertThat(compilation) 491 .hadErrorContaining("BadModule has errors") 492 .inFile(badComponent) 493 .onLine(7); 494 } 495 } 496