| /* |
| * Copyright (C) 2014 The Dagger Authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dagger.internal.codegen; |
| |
| import static com.google.testing.compile.CompilationSubject.assertThat; |
| import static dagger.internal.codegen.Compilers.compilerWithOptions; |
| import static dagger.internal.codegen.Compilers.daggerCompiler; |
| import static dagger.internal.codegen.TestUtils.message; |
| |
| import com.google.testing.compile.Compilation; |
| import com.google.testing.compile.JavaFileObjects; |
| import javax.tools.JavaFileObject; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| @RunWith(JUnit4.class) |
| public class ScopingValidationTest { |
| @Test |
| public void componentWithoutScopeIncludesScopedBindings_Fail() { |
| JavaFileObject componentFile = |
| JavaFileObjects.forSourceLines( |
| "test.MyComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Component(modules = ScopedModule.class)", |
| "interface MyComponent {", |
| " ScopedType string();", |
| "}"); |
| JavaFileObject typeFile = |
| JavaFileObjects.forSourceLines( |
| "test.ScopedType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "class ScopedType {", |
| " @Inject ScopedType(String s, long l, float f) {}", |
| "}"); |
| JavaFileObject moduleFile = |
| JavaFileObjects.forSourceLines( |
| "test.ScopedModule", |
| "package test;", |
| "", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Module", |
| "class ScopedModule {", |
| " @Provides @Singleton String string() { return \"a string\"; }", |
| " @Provides long integer() { return 0L; }", |
| " @Provides float floatingPoint() { return 0.0f; }", |
| "}"); |
| |
| Compilation compilation = daggerCompiler().compile(componentFile, typeFile, moduleFile); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "MyComponent (unscoped) may not reference scoped bindings:", |
| " @Singleton class ScopedType", |
| " @Provides @Singleton String ScopedModule.string()")); |
| } |
| |
| @Test // b/79859714 |
| public void bindsWithChildScope_inParentModule_notAllowed() { |
| JavaFileObject childScope = |
| JavaFileObjects.forSourceLines( |
| "test.ChildScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface ChildScope {}"); |
| |
| JavaFileObject foo = |
| JavaFileObjects.forSourceLines( |
| "test.Foo", |
| "package test;", |
| "", // |
| "interface Foo {}"); |
| |
| JavaFileObject fooImpl = |
| JavaFileObjects.forSourceLines( |
| "test.ChildModule", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class FooImpl implements Foo {", |
| " @Inject FooImpl() {}", |
| "}"); |
| |
| JavaFileObject parentModule = |
| JavaFileObjects.forSourceLines( |
| "test.ParentModule", |
| "package test;", |
| "", |
| "import dagger.Binds;", |
| "import dagger.Module;", |
| "", |
| "@Module", |
| "interface ParentModule {", |
| " @Binds @ChildScope Foo bind(FooImpl fooImpl);", |
| "}"); |
| |
| JavaFileObject parent = |
| JavaFileObjects.forSourceLines( |
| "test.ParentComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(modules = ParentModule.class)", |
| "interface Parent {", |
| " Child child();", |
| "}"); |
| |
| JavaFileObject child = |
| JavaFileObjects.forSourceLines( |
| "test.Child", |
| "package test;", |
| "", |
| "import dagger.Subcomponent;", |
| "", |
| "@ChildScope", |
| "@Subcomponent", |
| "interface Child {", |
| " Foo foo();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler().compile(childScope, foo, fooImpl, parentModule, parent, child); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "Parent scoped with @Singleton may not reference bindings with different " |
| + "scopes:", |
| " @Binds @ChildScope Foo ParentModule.bind(FooImpl)")); |
| } |
| |
| @Test |
| public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() { |
| JavaFileObject componentFile = |
| JavaFileObjects.forSourceLines( |
| "test.MyComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(modules = ScopedModule.class)", |
| "interface MyComponent {", |
| " ScopedType string();", |
| "}"); |
| JavaFileObject scopeFile = |
| JavaFileObjects.forSourceLines( |
| "test.PerTest", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface PerTest {}"); |
| JavaFileObject scopeWithAttribute = |
| JavaFileObjects.forSourceLines( |
| "test.Per", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface Per {", |
| " Class<?> value();", |
| "}"); |
| JavaFileObject typeFile = |
| JavaFileObjects.forSourceLines( |
| "test.ScopedType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@PerTest", // incompatible scope |
| "class ScopedType {", |
| " @Inject ScopedType(String s, long l, float f, boolean b) {}", |
| "}"); |
| JavaFileObject moduleFile = |
| JavaFileObjects.forSourceLines( |
| "test.ScopedModule", |
| "package test;", |
| "", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Module", |
| "class ScopedModule {", |
| " @Provides @PerTest String string() { return \"a string\"; }", // incompatible scope |
| " @Provides long integer() { return 0L; }", // unscoped - valid |
| " @Provides @Singleton float floatingPoint() { return 0.0f; }", // same scope - valid |
| " @Provides @Per(MyComponent.class) boolean bool() { return false; }", // incompatible |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler() |
| .compile(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "MyComponent scoped with @Singleton " |
| + "may not reference bindings with different scopes:", |
| " @PerTest class ScopedType", |
| " @Provides @PerTest String ScopedModule.string()", |
| " @Provides @Per(MyComponent.class) boolean " |
| + "ScopedModule.bool()")) |
| .inFile(componentFile) |
| .onLineContaining("interface MyComponent"); |
| |
| compilation = |
| compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") |
| .compile(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile); |
| // The @Inject binding for ScopedType should not appear here, but the @Singleton binding should. |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "ScopedModule contains bindings with different scopes:", |
| " @Provides @PerTest String ScopedModule.string()", |
| " @Provides @Singleton float ScopedModule.floatingPoint()", |
| " @Provides @Per(MyComponent.class) boolean " |
| + "ScopedModule.bool()")) |
| .inFile(moduleFile) |
| .onLineContaining("class ScopedModule"); |
| } |
| |
| @Test |
| public void fullBindingGraphValidationDoesNotReportForOneScope() { |
| Compilation compilation = |
| compilerWithOptions( |
| "-Adagger.fullBindingGraphValidation=ERROR", |
| "-Adagger.moduleHasDifferentScopesValidation=ERROR") |
| .compile( |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Module", |
| "interface TestModule {", |
| " @Provides @Singleton static Object object() { return \"object\"; }", |
| " @Provides @Singleton static String string() { return \"string\"; }", |
| " @Provides static int integer() { return 4; }", |
| "}")); |
| assertThat(compilation).succeededWithoutWarnings(); |
| } |
| |
| @Test |
| public void fullBindingGraphValidationDoesNotReportInjectBindings() { |
| Compilation compilation = |
| compilerWithOptions( |
| "-Adagger.fullBindingGraphValidation=ERROR", |
| "-Adagger.moduleHasDifferentScopesValidation=ERROR") |
| .compile( |
| JavaFileObjects.forSourceLines( |
| "test.UsedInRootRedScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@RedScope", |
| "final class UsedInRootRedScoped {", |
| " @Inject UsedInRootRedScoped() {}", |
| "}"), |
| JavaFileObjects.forSourceLines( |
| "test.UsedInRootBlueScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@BlueScope", |
| "final class UsedInRootBlueScoped {", |
| " @Inject UsedInRootBlueScoped() {}", |
| "}"), |
| JavaFileObjects.forSourceLines( |
| "test.RedScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface RedScope {}"), |
| JavaFileObjects.forSourceLines( |
| "test.BlueScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface BlueScope {}"), |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Module(subcomponents = Child.class)", |
| "interface TestModule {", |
| " @Provides @Singleton", |
| " static Object object(", |
| " UsedInRootRedScoped usedInRootRedScoped,", |
| " UsedInRootBlueScoped usedInRootBlueScoped) {", |
| " return \"object\";", |
| " }", |
| "}"), |
| JavaFileObjects.forSourceLines( |
| "test.Child", |
| "package test;", |
| "", |
| "import dagger.Subcomponent;", |
| "", |
| "@Subcomponent", |
| "interface Child {", |
| " UsedInChildRedScoped usedInChildRedScoped();", |
| " UsedInChildBlueScoped usedInChildBlueScoped();", |
| "", |
| " @Subcomponent.Builder", |
| " interface Builder {", |
| " Child child();", |
| " }", |
| "}"), |
| JavaFileObjects.forSourceLines( |
| "test.UsedInChildRedScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@RedScope", |
| "final class UsedInChildRedScoped {", |
| " @Inject UsedInChildRedScoped() {}", |
| "}"), |
| JavaFileObjects.forSourceLines( |
| "test.UsedInChildBlueScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@BlueScope", |
| "final class UsedInChildBlueScoped {", |
| " @Inject UsedInChildBlueScoped() {}", |
| "}")); |
| assertThat(compilation).succeededWithoutWarnings(); |
| } |
| |
| @Test |
| public void componentWithScopeCanDependOnMultipleScopedComponents() { |
| // If a scoped component will have dependencies, they can include multiple scoped component |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| " static class A { @Inject A() {} }", |
| " static class B { @Inject B() {} }", |
| "}"); |
| JavaFileObject simpleScope = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| JavaFileObject singletonScopedA = |
| JavaFileObjects.forSourceLines( |
| "test.SingletonComponentA", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface SingletonComponentA {", |
| " SimpleType.A type();", |
| "}"); |
| JavaFileObject singletonScopedB = |
| JavaFileObjects.forSourceLines( |
| "test.SingletonComponentB", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface SingletonComponentB {", |
| " SimpleType.B type();", |
| "}"); |
| JavaFileObject scopeless = |
| JavaFileObjects.forSourceLines( |
| "test.ScopelessComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@Component", |
| "interface ScopelessComponent {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject simpleScoped = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})", |
| "interface SimpleScopedComponent {", |
| " SimpleType.A type();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler() |
| .compile( |
| type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless); |
| assertThat(compilation).succeededWithoutWarnings(); |
| } |
| |
| |
| |
| // Tests the following component hierarchy: |
| // |
| // @ScopeA |
| // ComponentA |
| // [SimpleType getSimpleType()] |
| // / \ |
| // / \ |
| // @ScopeB @ScopeB |
| // ComponentB1 ComponentB2 |
| // \ [SimpleType getSimpleType()] |
| // \ / |
| // \ / |
| // @ScopeC |
| // ComponentC |
| // [SimpleType getSimpleType()] |
| @Test |
| public void componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond() { |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| JavaFileObject simpleScope = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| JavaFileObject scopeA = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| JavaFileObject scopeB = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| JavaFileObject componentA = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentA", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentA {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject componentB1 = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentB1", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentA.class)", |
| "interface ComponentB1 {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject componentB2 = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentB2", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentA.class)", |
| "interface ComponentB2 {", |
| "}"); |
| JavaFileObject componentC = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentC", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component(dependencies = {ComponentB1.class, ComponentB2.class})", |
| "interface ComponentC {", |
| " SimpleType type();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler() |
| .compile( |
| type, simpleScope, scopeA, scopeB, |
| componentA, componentB1, componentB2, componentC); |
| assertThat(compilation).succeededWithoutWarnings(); |
| } |
| |
| @Test |
| public void componentWithoutScopeCannotDependOnScopedComponent() { |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| JavaFileObject scopedComponent = |
| JavaFileObjects.forSourceLines( |
| "test.ScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface ScopedComponent {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject unscopedComponent = |
| JavaFileObjects.forSourceLines( |
| "test.UnscopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Component(dependencies = ScopedComponent.class)", |
| "interface UnscopedComponent {", |
| " SimpleType type();", |
| "}"); |
| |
| Compilation compilation = daggerCompiler().compile(type, scopedComponent, unscopedComponent); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "test.UnscopedComponent (unscoped) cannot depend on scoped components:", |
| " @Singleton test.ScopedComponent")); |
| } |
| |
| @Test |
| public void componentWithSingletonScopeMayNotDependOnOtherScope() { |
| // Singleton must be the widest lifetime of present scopes. |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| JavaFileObject simpleScope = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| JavaFileObject simpleScoped = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component", |
| "interface SimpleScopedComponent {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject singletonScoped = |
| JavaFileObjects.forSourceLines( |
| "test.SingletonComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(dependencies = SimpleScopedComponent.class)", |
| "interface SingletonComponent {", |
| " SimpleType type();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler().compile(type, simpleScope, simpleScoped, singletonScoped); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "This @Singleton component cannot depend on scoped components:", |
| " @test.SimpleScope test.SimpleScopedComponent")); |
| } |
| |
| @Test |
| public void componentScopeWithMultipleScopedDependenciesMustNotCycle() { |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| JavaFileObject scopeA = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| JavaFileObject scopeB = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| JavaFileObject longLifetime = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentLong", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentLong {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject mediumLifetime1 = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentMedium1", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentLong.class)", |
| "interface ComponentMedium1 {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject mediumLifetime2 = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentMedium2", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component", |
| "interface ComponentMedium2 {", |
| "}"); |
| JavaFileObject shortLifetime = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentShort", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component(dependencies = {ComponentMedium1.class, ComponentMedium2.class})", |
| "interface ComponentShort {", |
| " SimpleType type();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler() |
| .compile( |
| type, scopeA, scopeB, |
| longLifetime, mediumLifetime1, mediumLifetime2, shortLifetime); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "test.ComponentShort depends on scoped components in a non-hierarchical scope " |
| + "ordering:", |
| " @test.ScopeA test.ComponentLong", |
| " @test.ScopeB test.ComponentMedium1", |
| " @test.ScopeA test.ComponentShort")); |
| } |
| |
| @Test |
| public void componentScopeAncestryMustNotCycle() { |
| // The dependency relationship of components is necessarily from shorter lifetimes to |
| // longer lifetimes. The scoping annotations must reflect this, and so one cannot declare |
| // scopes on components such that they cycle. |
| JavaFileObject type = |
| JavaFileObjects.forSourceLines( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| JavaFileObject scopeA = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| JavaFileObject scopeB = |
| JavaFileObjects.forSourceLines( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| JavaFileObject longLifetime = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentLong", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentLong {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject mediumLifetime = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentMedium", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentLong.class)", |
| "interface ComponentMedium {", |
| " SimpleType type();", |
| "}"); |
| JavaFileObject shortLifetime = |
| JavaFileObjects.forSourceLines( |
| "test.ComponentShort", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component(dependencies = ComponentMedium.class)", |
| "interface ComponentShort {", |
| " SimpleType type();", |
| "}"); |
| |
| Compilation compilation = |
| daggerCompiler().compile(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "test.ComponentShort depends on scoped components in a non-hierarchical scope " |
| + "ordering:", |
| " @test.ScopeA test.ComponentLong", |
| " @test.ScopeB test.ComponentMedium", |
| " @test.ScopeA test.ComponentShort")); |
| |
| |
| // Test that compilation succeeds when transitive validation is disabled because the scope cycle |
| // cannot be detected. |
| compilation = |
| compilerWithOptions("-Adagger.validateTransitiveComponentDependencies=DISABLED") |
| .compile(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime); |
| assertThat(compilation).succeeded(); |
| } |
| |
| @Test |
| public void reusableNotAllowedOnComponent() { |
| JavaFileObject someComponent = |
| JavaFileObjects.forSourceLines( |
| "test.SomeComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import dagger.Reusable;", |
| "", |
| "@Reusable", |
| "@Component", |
| "interface SomeComponent {}"); |
| Compilation compilation = daggerCompiler().compile(someComponent); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining("@Reusable cannot be applied to components or subcomponents") |
| .inFile(someComponent) |
| .onLine(6); |
| } |
| |
| @Test |
| public void reusableNotAllowedOnSubcomponent() { |
| JavaFileObject someSubcomponent = |
| JavaFileObjects.forSourceLines( |
| "test.SomeComponent", |
| "package test;", |
| "", |
| "import dagger.Reusable;", |
| "import dagger.Subcomponent;", |
| "", |
| "@Reusable", |
| "@Subcomponent", |
| "interface SomeSubcomponent {}"); |
| Compilation compilation = daggerCompiler().compile(someSubcomponent); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining("@Reusable cannot be applied to components or subcomponents") |
| .inFile(someSubcomponent) |
| .onLine(6); |
| } |
| } |