/*
* Copyright (C) 2015 Google, Inc.
*
* 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 com.google.common.collect.ImmutableList;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
/** Tests for {@link dagger.Subcomponent.Builder} validation. */
@RunWith(JUnit4.class)
public class SubcomponentBuilderValidationTest {
private static final ErrorMessages.SubcomponentBuilderMessages MSGS =
new ErrorMessages.SubcomponentBuilderMessages();
@Test
public void testRefSubcomponentAndSubBuilderFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent child();",
" ChildComponent.Builder builder();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" static interface Builder {",
" ChildComponent build();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(String.format(MSGS.moreThanOneRefToSubcomponent(),
"test.ChildComponent", "[child(), builder()]"))
.in(componentFile);
}
@Test
public void testRefSubBuilderTwiceFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder builder1();",
" ChildComponent.Builder builder2();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" static interface Builder {",
" ChildComponent build();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(String.format(MSGS.moreThanOneRefToSubcomponent(),
"test.ChildComponent", "[builder1(), builder2()]"))
.in(componentFile);
}
@Test
public void testMoreThanOneBuilderFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder1 build();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" static interface Builder1 {",
" ChildComponent build();",
" }",
"",
" @Subcomponent.Builder",
" static interface Builder2 {",
" ChildComponent build();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(String.format(MSGS.moreThanOne(),
"[test.ChildComponent.Builder1, test.ChildComponent.Builder2]"))
.in(childComponentFile);
}
@Test
public void testBuilderGenericsFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder1 build();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" interface Builder<T> {",
" ChildComponent build();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.generics())
.in(childComponentFile);
}
@Test
public void testBuilderNotInComponentFails() {
JavaFileObject builder = JavaFileObjects.forSourceLines("test.Builder",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent.Builder",
"interface Builder {}");
assertAbout(javaSource()).that(builder)
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.mustBeInComponent())
.in(builder);
}
@Test
public void testBuilderMissingBuildMethodFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder1 build();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.missingBuildMethod())
.in(childComponentFile);
}
@Test
public void testPrivateBuilderFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" private interface Builder {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.isPrivate())
.in(childComponentFile);
}
@Test
public void testNonStaticBuilderFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" abstract class Builder {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.mustBeStatic())
.in(childComponentFile);
}
@Test
public void testNonAbstractBuilderFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" static class Builder {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.mustBeAbstract())
.in(childComponentFile);
}
@Test
public void testBuilderOneCxtorWithArgsFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" static abstract class Builder {",
" Builder(String unused) {}",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.cxtorOnlyOneAndNoArgs())
.in(childComponentFile);
}
@Test
public void testBuilderMoreThanOneCxtorFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" static abstract class Builder {",
" Builder() {}",
" Builder(String unused) {}",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.cxtorOnlyOneAndNoArgs())
.in(childComponentFile);
}
@Test
public void testBuilderEnumFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" enum Builder {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.mustBeClassOrInterface())
.in(childComponentFile);
}
@Test
public void testBuilderBuildReturnsWrongTypeFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" String build();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.buildMustReturnComponentType())
.in(childComponentFile).onLine(9);
}
@Test
public void testInheritedBuilderBuildReturnsWrongTypeFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent {",
" String build();",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.inheritedBuildMustReturnComponentType(), "build"))
.in(childComponentFile).onLine(12);
}
@Test
public void testTwoBuildMethodsFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" ChildComponent create();",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(String.format(MSGS.twoBuildMethods(), "build()"))
.in(childComponentFile).onLine(10);
}
@Test
public void testInheritedTwoBuildMethodsFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent {",
" ChildComponent build();",
" ChildComponent create();",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.inheritedTwoBuildMethods(), "create()", "build()"))
.in(childComponentFile).onLine(13);
}
@Test
public void testMoreThanOneArgFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" Builder set(String s, Integer i);",
" Builder set(Number n, Double d);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.methodsMustTakeOneArg())
.in(childComponentFile).onLine(10)
.and().withErrorContaining(MSGS.methodsMustTakeOneArg())
.in(childComponentFile).onLine(11);
}
@Test
public void testInheritedMoreThanOneArgFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent {",
" ChildComponent build();",
" Builder set1(String s, Integer i);",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.inheritedMethodsMustTakeOneArg(),
"set1(java.lang.String,java.lang.Integer)"))
.in(childComponentFile).onLine(13);
}
@Test
public void testSetterReturningNonVoidOrBuilderFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" String set(Integer i);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.methodsMustReturnVoidOrBuilder())
.in(childComponentFile).onLine(10);
}
@Test
public void testInheritedSetterReturningNonVoidOrBuilderFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent {",
" ChildComponent build();",
" String set(Integer i);",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.inheritedMethodsMustReturnVoidOrBuilder(),
"set(java.lang.Integer)"))
.in(childComponentFile).onLine(13);
}
@Test
public void testGenericsOnSetterMethodFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" <T> Builder set(T t);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(MSGS.methodsMayNotHaveTypeParameters())
.in(childComponentFile).onLine(10);
}
@Test
public void testGenericsOnInheritedSetterMethodFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent {",
" ChildComponent build();",
" <T> Builder set(T t);",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent {}",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.inheritedMethodsMayNotHaveTypeParameters(), "<T>set(T)"))
.in(childComponentFile).onLine(13);
}
@Test
public void testMultipleSettersPerTypeFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" void set1(String s);",
" void set2(String s);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.manyMethodsForType(),
"java.lang.String", "[set1(java.lang.String), set2(java.lang.String)]"))
.in(childComponentFile).onLine(8);
}
@Test
public void testMultipleSettersPerTypeIncludingResolvedGenericsFails() {
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"abstract class ChildComponent {",
" interface Parent<T> {",
" void set1(T t);",
" }",
"",
" @Subcomponent.Builder",
" interface Builder extends Parent<String> {",
" ChildComponent build();",
" void set2(String s);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.manyMethodsForType(),
"java.lang.String", "[set1(T), set2(java.lang.String)]"))
.in(childComponentFile).onLine(12);
}
@Test
public void testExtraSettersFails() {
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder build();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent",
"interface ChildComponent {",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent build();",
" void set1(String s);",
" void set2(Integer s);",
" }",
"}");
assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
String.format(MSGS.extraSetters(),
"[void test.ChildComponent.Builder.set1(String),"
+ " void test.ChildComponent.Builder.set2(Integer)]"))
.in(childComponentFile).onLine(8);
}
@Test
public void testMissingSettersFail() {
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" TestModule(String unused) {}",
" @Provides String s() { return null; }",
"}");
JavaFileObject module2File = JavaFileObjects.forSourceLines("test.Test2Module",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class Test2Module {",
" @Provides Integer i() { return null; }",
"}");
JavaFileObject module3File = JavaFileObjects.forSourceLines("test.Test3Module",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class Test3Module {",
" Test3Module(String unused) {}",
" @Provides Double d() { return null; }",
"}");
JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent",
"package test;",
"",
"import dagger.Component;",
"",
"import javax.inject.Provider;",
"",
"@Component",
"interface ParentComponent {",
" ChildComponent.Builder build();",
"}");
JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent",
"package test;",
"",
"import dagger.Subcomponent;",
"",
"@Subcomponent(modules = {TestModule.class, Test2Module.class, Test3Module.class})",
"interface ChildComponent {",
" String string();",
" Integer integer();",
"",
" @Subcomponent.Builder",
" interface Builder {",
" ChildComponent create();",
" }",
"}");
assertAbout(javaSources())
.that(ImmutableList.of(moduleFile,
module2File,
module3File,
componentFile,
childComponentFile))
.processedWith(new ComponentProcessor())
.failsToCompile()
.withErrorContaining(
// Ignores Test2Module because we can construct it ourselves.
// TODO(sameb): Ignore Test3Module because it's not used within transitive dependencies.
String.format(MSGS.missingSetters(), "[test.TestModule, test.Test3Module]"))
.in(childComponentFile).onLine(11);
}
}