blob: 2e7aa7d2abe0815e51d866fc59e11ccaadf22ad2 [file] [log] [blame]
/*
* 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.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 GraphValidationScopingTest {
@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(
"test.MyComponent (unscoped) may not reference scoped bindings:",
" @Singleton class test.ScopedType",
" @Provides @Singleton String test.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(
"test.Parent scoped with @Singleton may not reference bindings with different "
+ "scopes:",
" @Binds @test.ChildScope test.Foo test.ParentModule.bind(test.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(
"test.MyComponent scoped with @Singleton "
+ "may not reference bindings with different scopes:",
" @test.PerTest class test.ScopedType",
" @Provides @test.PerTest String test.ScopedModule.string()",
" @Provides @test.Per(test.MyComponent.class) boolean "
+ "test.ScopedModule.bool()"));
}
@Test public void componentWithScopeMayDependOnOnlyOneScopedComponent() {
// If a scoped component will have dependencies, they must only include, at most, a single
// 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).failed();
assertThat(compilation)
.hadErrorContaining(
message(
"@test.SimpleScope test.SimpleScopedComponent depends on more than one scoped "
+ "component:",
" @Singleton test.SingletonComponentA",
" @Singleton test.SingletonComponentB"));
}
@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 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
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);
}
}