blob: 162c8c022b81350ad9152b5c7651fb28bcee4737 [file] [log] [blame]
/*
* Copyright (C) 2016 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.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import dagger.Module;
import dagger.producers.ProducerModule;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public final class ModuleValidationTest {
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] {{ModuleType.MODULE}, {ModuleType.PRODUCER_MODULE}});
}
private enum ModuleType {
MODULE(Module.class),
PRODUCER_MODULE(ProducerModule.class),
;
private final Class<? extends Annotation> annotation;
ModuleType(Class<? extends Annotation> annotation) {
this.annotation = annotation;
}
String annotationWithSubcomponent(String subcomponent) {
return String.format("@%s(subcomponents = %s)", annotation.getSimpleName(), subcomponent);
}
String importStatement() {
return String.format("import %s;", annotation.getName());
}
String simpleName() {
return annotation.getSimpleName();
}
}
private final ModuleType moduleType;
public ModuleValidationTest(ModuleType moduleType) {
this.moduleType = moduleType;
}
@Test
public void moduleSubcomponents_notASubcomponent() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("NotASubcomponent.class"),
"class TestModule {}");
JavaFileObject notASubcomponent =
JavaFileObjects.forSourceLines(
"test.NotASubcomponent", "package test;", "", "class NotASubcomponent {}");
Compilation compilation = daggerCompiler().compile(module, notASubcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.NotASubcomponent is not a @Subcomponent or @ProductionSubcomponent")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_listsSubcomponentBuilder() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("Sub.Builder.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.Sub",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface Sub {",
" @Subcomponent.Builder",
" interface Builder {",
" Sub build();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.Sub.Builder is a @Subcomponent.Builder. Did you mean to use test.Sub?")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_listsSubcomponentFactory() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("Sub.Factory.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.Sub",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface Sub {",
" @Subcomponent.Factory",
" interface Factory {",
" Sub creator();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.Sub.Factory is a @Subcomponent.Factory. Did you mean to use test.Sub?")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_listsProductionSubcomponentBuilder() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("Sub.Builder.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.Sub",
"package test;",
"",
"import dagger.producers.ProductionSubcomponent;",
"",
"@ProductionSubcomponent",
"interface Sub {",
" @ProductionSubcomponent.Builder",
" interface Builder {",
" Sub build();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.Sub.Builder is a @ProductionSubcomponent.Builder. Did you mean to use test.Sub?")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_listsProductionSubcomponentFactory() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("Sub.Factory.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.Sub",
"package test;",
"",
"import dagger.producers.ProductionSubcomponent;",
"",
"@ProductionSubcomponent",
"interface Sub {",
" @ProductionSubcomponent.Factory",
" interface Factory {",
" Sub create();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.Sub.Factory is a @ProductionSubcomponent.Factory. Did you mean to use test.Sub?")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_noSubcomponentCreator() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("NoBuilder.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.NoBuilder",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface NoBuilder {}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.NoBuilder doesn't have a @Subcomponent.Builder or @Subcomponent.Factory, which "
+ "is required when used with @"
+ moduleType.simpleName()
+ ".subcomponents")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponents_noProductionSubcomponentCreator() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"",
moduleType.annotationWithSubcomponent("NoBuilder.class"),
"class TestModule {}");
JavaFileObject subcomponent =
JavaFileObjects.forSourceLines(
"test.NoBuilder",
"package test;",
"",
"import dagger.producers.ProductionSubcomponent;",
"",
"@ProductionSubcomponent",
"interface NoBuilder {}");
Compilation compilation = daggerCompiler().compile(module, subcomponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"test.NoBuilder doesn't have a @ProductionSubcomponent.Builder or "
+ "@ProductionSubcomponent.Factory, which is required when used with @"
+ moduleType.simpleName()
+ ".subcomponents")
.inFile(module)
.onLine(5);
}
@Test
public void moduleSubcomponentsAreTypes() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module(subcomponents = int.class)",
"class TestModule {}");
Compilation compilation = daggerCompiler().compile(module);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("int is not a valid subcomponent type")
.inFile(module)
.onLine(5);
}
@Test
public void tooManyAnnotations() {
assertThatModuleMethod(
"@BindsOptionalOf @Multibinds abstract Set<Object> tooManyAnnotations();")
.hasError("is annotated with more than one of");
}
@Test
public void invalidIncludedModule() {
JavaFileObject badModule =
JavaFileObjects.forSourceLines(
"test.BadModule",
"package test;",
"",
"import dagger.Binds;",
"import dagger.Module;",
"",
"@Module",
"abstract class BadModule {",
" @Binds abstract Object noParameters();",
"}");
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.IncludesBadModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module(includes = BadModule.class)",
"abstract class IncludesBadModule {}");
Compilation compilation = daggerCompiler().compile(badModule, module);
assertThat(compilation).hadErrorCount(2);
assertThat(compilation)
.hadErrorContaining("test.BadModule has errors")
.inFile(module)
.onLine(5);
assertThat(compilation)
.hadErrorContaining(
"@Binds methods must have exactly one parameter, whose type is assignable to the "
+ "return type")
.inFile(badModule)
.onLine(8);
}
@Test
public void scopeOnModule() {
JavaFileObject badModule =
JavaFileObjects.forSourceLines(
"test.BadModule",
"package test;",
"",
"import dagger.Module;",
"import javax.inject.Singleton;",
"",
"@Singleton",
"@Module",
"interface BadModule {}");
Compilation compilation = daggerCompiler().compile(badModule);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("@Modules cannot be scoped")
.inFile(badModule)
.onLineContaining("@Singleton");
}
@Test
public void moduleIncludesSelfCycle() {
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
moduleType.importStatement(),
"import dagger.Provides;",
"",
String.format("@%s(", moduleType.simpleName()),
" includes = {",
" TestModule.class, // first",
" OtherModule.class,",
" TestModule.class, // second",
" }",
")",
"class TestModule {",
" @Provides int i() { return 0; }",
"}");
JavaFileObject otherModule =
JavaFileObjects.forSourceLines(
"test.OtherModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module",
"class OtherModule {}");
Compilation compilation = daggerCompiler().compile(module, otherModule);
assertThat(compilation).failed();
String error = String.format("@%s cannot include themselves", moduleType.simpleName());
assertThat(compilation).hadErrorContaining(error).inFile(module).onLineContaining("Module(");
}
}