| /* |
| * 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 androidx.room.compiler.processing.XProcessingEnv; |
| import androidx.room.compiler.processing.util.Source; |
| import com.google.common.collect.ImmutableMap; |
| import dagger.testing.compile.CompilerTests; |
| 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() { |
| Source componentFile = |
| CompilerTests.javaSource( |
| "test.MyComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Component(modules = ScopedModule.class)", |
| "interface MyComponent {", |
| " ScopedType string();", |
| "}"); |
| Source typeFile = |
| CompilerTests.javaSource( |
| "test.ScopedType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "class ScopedType {", |
| " @Inject ScopedType(String s, long l, float f) {}", |
| "}"); |
| Source moduleFile = |
| CompilerTests.javaSource( |
| "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; }", |
| "}"); |
| |
| CompilerTests.daggerCompiler(componentFile, typeFile, moduleFile) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "MyComponent (unscoped) may not reference scoped bindings:", |
| " @Singleton class ScopedType", |
| " ScopedType is requested at", |
| " [MyComponent] MyComponent.string()", |
| "", |
| " @Provides @Singleton String ScopedModule.string()")); |
| }); |
| } |
| |
| @Test // b/79859714 |
| public void bindsWithChildScope_inParentModule_notAllowed() { |
| Source childScope = |
| CompilerTests.javaSource( |
| "test.ChildScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface ChildScope {}"); |
| |
| Source foo = |
| CompilerTests.javaSource( |
| "test.Foo", |
| "package test;", |
| "", // |
| "interface Foo {}"); |
| |
| Source fooImpl = |
| CompilerTests.javaSource( |
| "test.FooImpl", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class FooImpl implements Foo {", |
| " @Inject FooImpl() {}", |
| "}"); |
| |
| Source parentModule = |
| CompilerTests.javaSource( |
| "test.ParentModule", |
| "package test;", |
| "", |
| "import dagger.Binds;", |
| "import dagger.Module;", |
| "", |
| "@Module", |
| "interface ParentModule {", |
| " @Binds @ChildScope Foo bind(FooImpl fooImpl);", |
| "}"); |
| |
| Source parent = |
| CompilerTests.javaSource( |
| "test.Parent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(modules = ParentModule.class)", |
| "interface Parent {", |
| " Child child();", |
| "}"); |
| |
| Source child = |
| CompilerTests.javaSource( |
| "test.Child", |
| "package test;", |
| "", |
| "import dagger.Subcomponent;", |
| "", |
| "@ChildScope", |
| "@Subcomponent", |
| "interface Child {", |
| " Foo foo();", |
| "}"); |
| |
| CompilerTests.daggerCompiler(childScope, foo, fooImpl, parentModule, parent, child) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "Parent scoped with @Singleton may not reference bindings with different " |
| + "scopes:", |
| " @Binds @ChildScope Foo ParentModule.bind(FooImpl)")); |
| }); |
| } |
| |
| @Test |
| public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() { |
| Source componentFile = |
| CompilerTests.javaSource( |
| "test.MyComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(modules = ScopedModule.class)", |
| "interface MyComponent {", |
| " ScopedType string();", |
| "}"); |
| Source scopeFile = |
| CompilerTests.javaSource( |
| "test.PerTest", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface PerTest {}"); |
| Source scopeWithAttribute = |
| CompilerTests.javaSource( |
| "test.Per", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface Per {", |
| " Class<?> value();", |
| "}"); |
| Source typeFile = |
| CompilerTests.javaSource( |
| "test.ScopedType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@PerTest", // incompatible scope |
| "class ScopedType {", |
| " @Inject ScopedType(String s, long l, float f, boolean b) {}", |
| "}"); |
| Source moduleFile = |
| CompilerTests.javaSource( |
| "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 |
| "}"); |
| |
| CompilerTests.daggerCompiler(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject |
| .hasErrorContaining( |
| String.join( |
| "\n", |
| "MyComponent scoped with @Singleton may not reference bindings with " |
| + "different scopes:", |
| " @PerTest class ScopedType", |
| " ScopedType is requested at", |
| " [MyComponent] MyComponent.string()", |
| "", |
| " @Provides @PerTest String ScopedModule.string()", |
| "", |
| // TODO(b/241293838): Remove dependency on backend once this bug is fixed. |
| CompilerTests.backend(subject).equals(XProcessingEnv.Backend.JAVAC) |
| ? " @Provides @Per(MyComponent.class) boolean ScopedModule.bool()" |
| : " @Provides @Per(MyComponent) boolean ScopedModule.bool()")) |
| .onSource(componentFile) |
| .onLineContaining("interface MyComponent"); |
| }); |
| |
| // The @Inject binding for ScopedType should not appear here, but the @Singleton binding should. |
| CompilerTests.daggerCompiler(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile) |
| .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(2); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "ScopedModule contains bindings with different scopes:", |
| " @Provides @PerTest String ScopedModule.string()", |
| "", |
| " @Provides @Singleton float ScopedModule.floatingPoint()", |
| "", |
| // TODO(b/241293838): Remove dependency on backend once this bug is fixed. |
| CompilerTests.backend(subject).equals(XProcessingEnv.Backend.JAVAC) |
| ? " @Provides @Per(MyComponent.class) boolean ScopedModule.bool()" |
| : " @Provides @Per(MyComponent) boolean ScopedModule.bool()")) |
| .onSource(moduleFile) |
| .onLineContaining("class ScopedModule"); |
| }); |
| } |
| |
| @Test |
| public void fullBindingGraphValidationDoesNotReportForOneScope() { |
| CompilerTests.daggerCompiler( |
| CompilerTests.javaSource( |
| "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; }", |
| "}")) |
| .withProcessingOptions( |
| ImmutableMap.<String, String>builder() |
| .put("dagger.fullBindingGraphValidation", "ERROR") |
| .put("dagger.moduleHasDifferentScopesValidation", "ERROR") |
| .buildOrThrow()) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(0); |
| subject.hasWarningCount(0); |
| }); |
| } |
| |
| @Test |
| public void fullBindingGraphValidationDoesNotReportInjectBindings() { |
| CompilerTests.daggerCompiler( |
| CompilerTests.javaSource( |
| "test.UsedInRootRedScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@RedScope", |
| "final class UsedInRootRedScoped {", |
| " @Inject UsedInRootRedScoped() {}", |
| "}"), |
| CompilerTests.javaSource( |
| "test.UsedInRootBlueScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@BlueScope", |
| "final class UsedInRootBlueScoped {", |
| " @Inject UsedInRootBlueScoped() {}", |
| "}"), |
| CompilerTests.javaSource( |
| "test.RedScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface RedScope {}"), |
| CompilerTests.javaSource( |
| "test.BlueScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope", |
| "@interface BlueScope {}"), |
| CompilerTests.javaSource( |
| "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\";", |
| " }", |
| "}"), |
| CompilerTests.javaSource( |
| "test.Child", |
| "package test;", |
| "", |
| "import dagger.Subcomponent;", |
| "", |
| "@Subcomponent", |
| "interface Child {", |
| " UsedInChildRedScoped usedInChildRedScoped();", |
| " UsedInChildBlueScoped usedInChildBlueScoped();", |
| "", |
| " @Subcomponent.Builder", |
| " interface Builder {", |
| " Child child();", |
| " }", |
| "}"), |
| CompilerTests.javaSource( |
| "test.UsedInChildRedScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@RedScope", |
| "final class UsedInChildRedScoped {", |
| " @Inject UsedInChildRedScoped() {}", |
| "}"), |
| CompilerTests.javaSource( |
| "test.UsedInChildBlueScoped", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "@BlueScope", |
| "final class UsedInChildBlueScoped {", |
| " @Inject UsedInChildBlueScoped() {}", |
| "}")) |
| .withProcessingOptions( |
| ImmutableMap.<String, String>builder() |
| .put("dagger.fullBindingGraphValidation", "ERROR") |
| .put("dagger.moduleHasDifferentScopesValidation", "ERROR") |
| .buildOrThrow()) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(0); |
| subject.hasWarningCount(0); |
| }); |
| } |
| |
| @Test |
| public void componentWithScopeCanDependOnMultipleScopedComponents() { |
| // If a scoped component will have dependencies, they can include multiple scoped component |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| " static class A { @Inject A() {} }", |
| " static class B { @Inject B() {} }", |
| "}"); |
| Source simpleScope = |
| CompilerTests.javaSource( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| Source singletonScopedA = |
| CompilerTests.javaSource( |
| "test.SingletonComponentA", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface SingletonComponentA {", |
| " SimpleType.A type();", |
| "}"); |
| Source singletonScopedB = |
| CompilerTests.javaSource( |
| "test.SingletonComponentB", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface SingletonComponentB {", |
| " SimpleType.B type();", |
| "}"); |
| Source scopeless = |
| CompilerTests.javaSource( |
| "test.ScopelessComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@Component", |
| "interface ScopelessComponent {", |
| " SimpleType type();", |
| "}"); |
| Source simpleScoped = |
| CompilerTests.javaSource( |
| "test.SimpleScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})", |
| "interface SimpleScopedComponent {", |
| " SimpleType.A type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler( |
| type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(0); |
| subject.hasWarningCount(0); |
| }); |
| } |
| |
| |
| |
| // Tests the following component hierarchy: |
| // |
| // @ScopeA |
| // ComponentA |
| // [SimpleType getSimpleType()] |
| // / \ |
| // / \ |
| // @ScopeB @ScopeB |
| // ComponentB1 ComponentB2 |
| // \ [SimpleType getSimpleType()] |
| // \ / |
| // \ / |
| // @ScopeC |
| // ComponentC |
| // [SimpleType getSimpleType()] |
| @Test |
| public void componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond() { |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| Source simpleScope = |
| CompilerTests.javaSource( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| Source scopeA = |
| CompilerTests.javaSource( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| Source scopeB = |
| CompilerTests.javaSource( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| Source componentA = |
| CompilerTests.javaSource( |
| "test.ComponentA", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentA {", |
| " SimpleType type();", |
| "}"); |
| Source componentB1 = |
| CompilerTests.javaSource( |
| "test.ComponentB1", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentA.class)", |
| "interface ComponentB1 {", |
| " SimpleType type();", |
| "}"); |
| Source componentB2 = |
| CompilerTests.javaSource( |
| "test.ComponentB2", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentA.class)", |
| "interface ComponentB2 {", |
| "}"); |
| Source componentC = |
| CompilerTests.javaSource( |
| "test.ComponentC", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component(dependencies = {ComponentB1.class, ComponentB2.class})", |
| "interface ComponentC {", |
| " SimpleType type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler( |
| type, simpleScope, scopeA, scopeB, componentA, componentB1, componentB2, componentC) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(0); |
| subject.hasWarningCount(0); |
| }); |
| } |
| |
| @Test |
| public void componentWithoutScopeCannotDependOnScopedComponent() { |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| Source scopedComponent = |
| CompilerTests.javaSource( |
| "test.ScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component", |
| "interface ScopedComponent {", |
| " SimpleType type();", |
| "}"); |
| Source unscopedComponent = |
| CompilerTests.javaSource( |
| "test.UnscopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Component(dependencies = ScopedComponent.class)", |
| "interface UnscopedComponent {", |
| " SimpleType type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler(type, scopedComponent, unscopedComponent) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "test.UnscopedComponent (unscoped) cannot depend on scoped components:", |
| " @Singleton test.ScopedComponent")); |
| }); |
| } |
| |
| @Test |
| public void componentWithSingletonScopeMayNotDependOnOtherScope() { |
| // Singleton must be the widest lifetime of present scopes. |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| Source simpleScope = |
| CompilerTests.javaSource( |
| "test.SimpleScope", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface SimpleScope {}"); |
| Source simpleScoped = |
| CompilerTests.javaSource( |
| "test.SimpleScopedComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@SimpleScope", |
| "@Component", |
| "interface SimpleScopedComponent {", |
| " SimpleType type();", |
| "}"); |
| Source singletonScoped = |
| CompilerTests.javaSource( |
| "test.SingletonComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(dependencies = SimpleScopedComponent.class)", |
| "interface SingletonComponent {", |
| " SimpleType type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler(type, simpleScope, simpleScoped, singletonScoped) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "This @Singleton component cannot depend on scoped components:", |
| " @test.SimpleScope test.SimpleScopedComponent")); |
| }); |
| } |
| |
| @Test |
| public void componentScopeWithMultipleScopedDependenciesMustNotCycle() { |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| Source scopeA = |
| CompilerTests.javaSource( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| Source scopeB = |
| CompilerTests.javaSource( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| Source longLifetime = |
| CompilerTests.javaSource( |
| "test.ComponentLong", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentLong {", |
| " SimpleType type();", |
| "}"); |
| Source mediumLifetime1 = |
| CompilerTests.javaSource( |
| "test.ComponentMedium1", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentLong.class)", |
| "interface ComponentMedium1 {", |
| " SimpleType type();", |
| "}"); |
| Source mediumLifetime2 = |
| CompilerTests.javaSource( |
| "test.ComponentMedium2", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component", |
| "interface ComponentMedium2 {", |
| "}"); |
| Source shortLifetime = |
| CompilerTests.javaSource( |
| "test.ComponentShort", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component(dependencies = {ComponentMedium1.class, ComponentMedium2.class})", |
| "interface ComponentShort {", |
| " SimpleType type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler( |
| type, scopeA, scopeB, longLifetime, mediumLifetime1, mediumLifetime2, shortLifetime) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "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. |
| Source type = |
| CompilerTests.javaSource( |
| "test.SimpleType", |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "class SimpleType {", |
| " @Inject SimpleType() {}", |
| "}"); |
| Source scopeA = |
| CompilerTests.javaSource( |
| "test.ScopeA", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeA {}"); |
| Source scopeB = |
| CompilerTests.javaSource( |
| "test.ScopeB", |
| "package test;", |
| "", |
| "import javax.inject.Scope;", |
| "", |
| "@Scope @interface ScopeB {}"); |
| Source longLifetime = |
| CompilerTests.javaSource( |
| "test.ComponentLong", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component", |
| "interface ComponentLong {", |
| " SimpleType type();", |
| "}"); |
| Source mediumLifetime = |
| CompilerTests.javaSource( |
| "test.ComponentMedium", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeB", |
| "@Component(dependencies = ComponentLong.class)", |
| "interface ComponentMedium {", |
| " SimpleType type();", |
| "}"); |
| Source shortLifetime = |
| CompilerTests.javaSource( |
| "test.ComponentShort", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "", |
| "@ScopeA", |
| "@Component(dependencies = ComponentMedium.class)", |
| "interface ComponentShort {", |
| " SimpleType type();", |
| "}"); |
| |
| CompilerTests.daggerCompiler(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| String.join( |
| "\n", |
| "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. |
| CompilerTests.daggerCompiler(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime) |
| .withProcessingOptions( |
| ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED")) |
| .compile(subject -> subject.hasErrorCount(0)); |
| } |
| |
| @Test |
| public void reusableNotAllowedOnComponent() { |
| Source someComponent = |
| CompilerTests.javaSource( |
| "test.SomeComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import dagger.Reusable;", |
| "", |
| "@Reusable", |
| "@Component", |
| "interface SomeComponent {}"); |
| CompilerTests.daggerCompiler(someComponent) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| "@Reusable cannot be applied to components or subcomponents") |
| .onSource(someComponent) |
| .onLine(6); |
| }); |
| } |
| |
| @Test |
| public void reusableNotAllowedOnSubcomponent() { |
| Source someSubcomponent = |
| CompilerTests.javaSource( |
| "test.SomeComponent", |
| "package test;", |
| "", |
| "import dagger.Reusable;", |
| "import dagger.Subcomponent;", |
| "", |
| "@Reusable", |
| "@Subcomponent", |
| "interface SomeSubcomponent {}"); |
| CompilerTests.daggerCompiler(someSubcomponent) |
| .compile( |
| subject -> { |
| subject.hasErrorCount(1); |
| subject.hasErrorContaining( |
| "@Reusable cannot be applied to components or subcomponents") |
| .onSource(someSubcomponent) |
| .onLine(6); |
| }); |
| } |
| } |