/*
 * Copyright (C) 2015 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.CompilerMode.DEFAULT_MODE;
import static dagger.internal.codegen.TestUtils.message;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY;
import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER;
import static dagger.internal.codegen.binding.ComponentCreatorKind.FACTORY;
import static dagger.internal.codegen.binding.ComponentKind.SUBCOMPONENT;
import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.moreThanOneRefToSubcomponent;
import static dagger.internal.codegen.binding.ErrorMessages.componentMessagesFor;

import com.google.common.collect.ImmutableList;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import dagger.internal.codegen.binding.ComponentCreatorAnnotation;
import java.util.Collection;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/** Tests for {@link dagger.Subcomponent.Builder} validation. */
@RunWith(Parameterized.class)
public class SubcomponentCreatorValidationTest extends ComponentCreatorTestHelper {
  @Parameters(name = "creatorKind={0}")
  public static Collection<Object[]> parameters() {
    return ImmutableList.copyOf(new Object[][] {{SUBCOMPONENT_BUILDER}, {SUBCOMPONENT_FACTORY}});
  }

  public SubcomponentCreatorValidationTest(ComponentCreatorAnnotation componentCreatorAnnotation) {
    super(DEFAULT_MODE, componentCreatorAnnotation);
  }

  @Test
  public void testRefSubcomponentAndSubCreatorFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent child();",
        "  ChildComponent.Builder builder();",
        "}");
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "interface ChildComponent {",
        "  @Subcomponent.Builder",
        "  static interface Builder {",
        "    ChildComponent build();",
        "  }",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                moreThanOneRefToSubcomponent(),
                "test.ChildComponent",
                process("[child(), builder()]")))
        .inFile(componentFile);
  }

  @Test
  public void testRefSubCreatorTwiceFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder builder1();",
        "  ChildComponent.Builder builder2();",
        "}");
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "interface ChildComponent {",
        "  @Subcomponent.Builder",
        "  static interface Builder {",
        "    ChildComponent build();",
        "  }",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                moreThanOneRefToSubcomponent(),
                "test.ChildComponent", process("[builder1(), builder2()]")))
        .inFile(componentFile);
  }

  @Test
  public void testMoreThanOneCreatorFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder1 build();",
        "}");
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "interface ChildComponent {",
        "  @Subcomponent.Builder",
        "  static interface Builder1 {",
        "    ChildComponent build();",
        "  }",
        "",
        "  @Subcomponent.Builder",
        "  static interface Builder2 {",
        "    ChildComponent build();",
        "  }",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                componentMessagesFor(SUBCOMPONENT).moreThanOne(),
                process("[test.ChildComponent.Builder1, test.ChildComponent.Builder2]")))
        .inFile(childComponentFile);
  }

  @Test
  public void testMoreThanOneCreatorFails_differentTypes() {
    JavaFileObject componentFile = preprocessedJavaFile("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",
        "  static interface Builder {",
        "    ChildComponent build();",
        "  }",
        "",
        "  @Subcomponent.Factory",
        "  static interface Factory {",
        "    ChildComponent create();",
        "  }",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                componentMessagesFor(SUBCOMPONENT).moreThanOne(),
                "[test.ChildComponent.Builder, test.ChildComponent.Factory]"))
        .inFile(childComponentFile);
  }

  @Test
  public void testCreatorGenericsFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder build();",
        "}");
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "interface ChildComponent {",
        "  @Subcomponent.Builder",
        "  interface Builder<T> {",
        "     ChildComponent build();",
        "  }",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation).hadErrorContaining(messages.generics()).inFile(childComponentFile);
  }

  @Test
  public void testCreatorNotInComponentFails() {
    JavaFileObject builder = preprocessedJavaFile("test.Builder",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent.Builder",
        "interface Builder {}");
    Compilation compilation = compile(builder);
    assertThat(compilation).failed();
    assertThat(compilation).hadErrorContaining(messages.mustBeInComponent()).inFile(builder);
  }

  @Test
  public void testCreatorMissingFactoryMethodFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder builder();",
        "}");
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "interface ChildComponent {",
        "  @Subcomponent.Builder",
        "  interface Builder {}",
        "}");
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.missingFactoryMethod())
        .inFile(childComponentFile);
  }

  @Test
  public void testPrivateCreatorFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  private interface Builder {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation).hadErrorContaining(messages.isPrivate()).inFile(childComponentFile);
  }

  @Test
  public void testNonStaticCreatorFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  abstract class Builder {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation).hadErrorContaining(messages.mustBeStatic()).inFile(childComponentFile);
  }

  @Test
  public void testNonAbstractCreatorFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  static class Builder {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.mustBeAbstract())
        .inFile(childComponentFile);
  }

  @Test
  public void testCreatorOneConstructorWithArgsFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  static abstract class Builder {",
        "    Builder(String unused) {}",
        "  }",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.invalidConstructor())
        .inFile(childComponentFile);
  }

  @Test
  public void testCreatorMoreThanOneConstructorFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  static abstract class Builder {",
        "    Builder() {}",
        "    Builder(String unused) {}",
        "  }",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.invalidConstructor())
        .inFile(childComponentFile);
  }

  @Test
  public void testCreatorEnumFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  enum Builder {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.mustBeClassOrInterface())
        .inFile(childComponentFile);
  }

  @Test
  public void testCreatorFactoryMethodReturnsWrongTypeFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  interface Builder {",
        "    String build();",
        "  }",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(messages.factoryMethodMustReturnComponentType())
        .inFile(childComponentFile)
        .onLine(9);
  }

  @Test
  public void testInheritedCreatorFactoryMethodReturnsWrongTypeFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  interface Parent {",
        "    String build();",
        "  }",
        "",
        "  @Subcomponent.Builder",
        "  interface Builder extends Parent {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                messages.inheritedFactoryMethodMustReturnComponentType(), process("build")))
        .inFile(childComponentFile)
        .onLine(12);
  }

  @Test
  public void testTwoFactoryMethodsFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  @Subcomponent.Builder",
        "  interface Builder {",
        "    ChildComponent build();",
        "    ChildComponent build1();",
        "  }",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(String.format(messages.twoFactoryMethods(), process("build()")))
        .inFile(childComponentFile)
        .onLine(10);
  }

  @Test
  public void testInheritedTwoFactoryMethodsFails() {
    JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent",
        "package test;",
        "",
        "import dagger.Subcomponent;",
        "",
        "@Subcomponent",
        "abstract class ChildComponent {",
        "  interface Parent {",
        "    ChildComponent build();",
        "    ChildComponent build1();",
        "  }",
        "",
        "  @Subcomponent.Builder",
        "  interface Builder extends Parent {}",
        "}");
    Compilation compilation = compile(childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                messages.inheritedTwoFactoryMethods(), process("build()"), process("build1()")))
        .inFile(childComponentFile)
        .onLine(13);
  }

  @Test
  public void testMultipleSettersPerTypeFails() {
    JavaFileObject moduleFile =
        JavaFileObjects.forSourceLines(
            "test.TestModule",
            "package test;",
            "",
            "import dagger.Module;",
            "import dagger.Provides;",
            "",
            "@Module",
            "final class TestModule {",
            "  @Provides String s() { return \"\"; }",
            "}");
    JavaFileObject componentFile =
        preprocessedJavaFile(
            "test.ParentComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component",
            "interface ParentComponent {",
            "  ChildComponent.Builder childComponentBuilder();",
            "}");
    JavaFileObject childComponentFile =
        javaFileBuilder("test.ChildComponent")
            .addLines(
                "package test;",
                "",
                "import dagger.Subcomponent;",
                "import javax.inject.Provider;",
                "",
                "@Subcomponent(modules = TestModule.class)",
                "abstract class ChildComponent {",
                "  abstract String s();",
                "")
            .addLinesIf(
                BUILDER,
                "  @Subcomponent.Builder",
                "  interface Builder {",
                "    ChildComponent build();",
                "    void set1(TestModule s);",
                "    void set2(TestModule s);",
                "  }")
            .addLinesIf(
                FACTORY,
                "  @Subcomponent.Factory",
                "  interface Factory {",
                "    ChildComponent create(TestModule m1, TestModule m2);",
                "  }")
            .addLines( //
                "}")
            .build();
    Compilation compilation = compile(moduleFile, componentFile, childComponentFile);
    assertThat(compilation).failed();
    String elements =
        creatorKind.equals(BUILDER)
            ? "[void test.ChildComponent.Builder.set1(test.TestModule), "
                + "void test.ChildComponent.Builder.set2(test.TestModule)]"
            : "[test.TestModule m1, test.TestModule m2]";
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                messages.multipleSettersForModuleOrDependencyType(), "test.TestModule", elements))
        .inFile(childComponentFile)
        .onLine(11);
  }

  @Test
  public void testMultipleSettersPerTypeIncludingResolvedGenericsFails() {
    JavaFileObject moduleFile =
        JavaFileObjects.forSourceLines(
            "test.TestModule",
            "package test;",
            "",
            "import dagger.Module;",
            "import dagger.Provides;",
            "",
            "@Module",
            "final class TestModule {",
            "  @Provides String s() { return \"\"; }",
            "}");
    JavaFileObject componentFile =
        preprocessedJavaFile(
            "test.ParentComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component",
            "interface ParentComponent {",
            "  ChildComponent.Builder childComponentBuilder();",
            "}");
    JavaFileObject childComponentFile =
        javaFileBuilder("test.ChildComponent")
            .addLines(
                "package test;",
                "",
                "import dagger.Subcomponent;",
                "import javax.inject.Provider;",
                "",
                "@Subcomponent(modules = TestModule.class)",
                "abstract class ChildComponent {",
                "  abstract String s();",
                "")
            .addLinesIf(
                BUILDER,
                "  interface Parent<T> {",
                "    void set1(T t);",
                "  }",
                "",
                "  @Subcomponent.Builder",
                "  interface Builder extends Parent<TestModule> {",
                "    ChildComponent build();",
                "    void set2(TestModule s);",
                "  }")
            .addLinesIf(
                FACTORY,
                "  interface Parent<C, T> {",
                "    C create(TestModule m1, T t);",
                "  }",
                "",
                "  @Subcomponent.Factory",
                "  interface Factory extends Parent<ChildComponent, TestModule> {}")
            .addLines( //
                "}")
            .build();
    Compilation compilation = compile(moduleFile, componentFile, childComponentFile);
    assertThat(compilation).failed();
    String elements =
        creatorKind.equals(BUILDER)
            ? "[void test.ChildComponent.Builder.set1(test.TestModule), "
                + "void test.ChildComponent.Builder.set2(test.TestModule)]"
            : "[test.TestModule m1, test.TestModule t]";
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                messages.multipleSettersForModuleOrDependencyType(), "test.TestModule", elements))
        .inFile(childComponentFile)
        .onLine(15);
  }

  @Test
  public void testMultipleSettersPerBoundInstanceTypeFails() {
    JavaFileObject componentFile =
        preprocessedJavaFile(
            "test.ParentComponent",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@Component",
            "interface ParentComponent {",
            "  ChildComponent.Builder childComponentBuilder();",
            "}");
    JavaFileObject childComponentFile =
        javaFileBuilder("test.ChildComponent")
            .addLines(
                "package test;",
                "",
                "import dagger.BindsInstance;",
                "import dagger.Subcomponent;",
                "",
                "@Subcomponent",
                "abstract class ChildComponent {",
                "  abstract String s();",
                "")
            .addLinesIf(
                BUILDER,
                "  @Subcomponent.Builder",
                "  interface Builder {",
                "    ChildComponent build();",
                "    @BindsInstance void set1(String s);",
                "    @BindsInstance void set2(String s);",
                "  }")
            .addLinesIf(
                FACTORY,
                "  @Subcomponent.Factory",
                "  interface Factory {",
                "    ChildComponent create(",
                "        @BindsInstance String s1, @BindsInstance String s2);",
                "  }")
            .addLines( //
                "}")
            .build();

    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    String firstBinding = creatorKind.equals(FACTORY)
        ? "ChildComponent.Factory.create(s1, …)"
        : "@BindsInstance void ChildComponent.Builder.set1(String)";
    String secondBinding = creatorKind.equals(FACTORY)
        ? "ChildComponent.Factory.create(…, s2)"
        : "@BindsInstance void ChildComponent.Builder.set2(String)";
    assertThat(compilation)
        .hadErrorContaining(
            message(
                "String is bound multiple times:",
                "    " + firstBinding,
                "    " + secondBinding,
                "    in component: [ParentComponent → ChildComponent]"))
        .inFile(componentFile)
        .onLineContaining("interface ParentComponent {");
  }

  @Test
  public void testExtraSettersFails() {
    JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder build();",
        "}");
    JavaFileObject childComponentFile =
        javaFileBuilder("test.ChildComponent")
            .addLines(
                "package test;",
                "",
                "import dagger.Subcomponent;",
                "import javax.inject.Provider;",
                "",
                "@Subcomponent",
                "abstract class ChildComponent {")
            .addLinesIf(
                BUILDER,
                "  @Subcomponent.Builder",
                "  interface Builder {",
                "    ChildComponent build();",
                "    void set1(String s);",
                "    void set2(Integer s);",
                "  }")
            .addLinesIf(
                FACTORY,
                "  @Subcomponent.Factory",
                "  interface Factory {",
                "    ChildComponent create(String s, Integer i);",
                "  }")
            .addLines( //
                "}")
            .build();
    Compilation compilation = compile(componentFile, childComponentFile);
    assertThat(compilation).failed();
    String elements =
        creatorKind.equals(FACTORY)
            ? "[String s, Integer i]"
            : "[void test.ChildComponent.Builder.set1(String),"
                + " void test.ChildComponent.Builder.set2(Integer)]";
    assertThat(compilation)
        .hadErrorContaining(
            String.format(
                messages.extraSetters(),
                elements))
        .inFile(childComponentFile)
        .onLine(9);
  }

  @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 = preprocessedJavaFile("test.ParentComponent",
        "package test;",
        "",
        "import dagger.Component;",
        "import javax.inject.Provider;",
        "",
        "@Component",
        "interface ParentComponent {",
        "  ChildComponent.Builder build();",
        "}");
    JavaFileObject childComponentFile =
        preprocessedJavaFile(
            "test.TestComponent",
            "package test;",
            "",
            "import dagger.Subcomponent;",
            "",
            "@Subcomponent(modules = {TestModule.class, Test2Module.class, Test3Module.class})",
            "interface ChildComponent {",
            "  String string();",
            "  Integer integer();",
            "",
            "  @Subcomponent.Builder",
            "  interface Builder {",
            "    ChildComponent build();",
            "  }",
            "}");
    Compilation compilation =
        compile(moduleFile, module2File, module3File, componentFile, childComponentFile);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            // Ignores Test2Module because we can construct it ourselves.
            // TODO(sameb): Ignore Test3Module because it's not used within transitive dependencies.
            String.format(messages.missingSetters(), "[test.TestModule, test.Test3Module]"))
        .inFile(childComponentFile)
        .onLine(11);
  }

  @Test
  public void covariantFactoryMethodReturnType() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "test.Foo",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject supertype =
        JavaFileObjects.forSourceLines(
            "test.Supertype",
            "package test;",
            "",
            "interface Supertype {",
            "  Foo foo();",
            "}");

    JavaFileObject subcomponent =
        preprocessedJavaFile(
            "test.HasSupertype",
            "package test;",
            "",
            "import dagger.Subcomponent;",
            "",
            "@Subcomponent",
            "interface HasSupertype extends Supertype {",
            "  @Subcomponent.Builder",
            "  interface Builder {",
            "    Supertype build();",
            "  }",
            "}");

    Compilation compilation = compile(foo, supertype, subcomponent);
    assertThat(compilation).succeededWithoutWarnings();
  }

  @Test
  public void covariantFactoryMethodReturnType_hasNewMethod() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "test.Foo",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject bar =
        JavaFileObjects.forSourceLines(
            "test.Bar",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Bar {",
            "  @Inject Bar() {}",
            "}");
    JavaFileObject supertype =
        JavaFileObjects.forSourceLines(
            "test.Supertype",
            "package test;",
            "",
            "interface Supertype {",
            "  Foo foo();",
            "}");

    JavaFileObject subcomponent =
        preprocessedJavaFile(
            "test.HasSupertype",
            "package test;",
            "",
            "import dagger.Subcomponent;",
            "",
            "@Subcomponent",
            "interface HasSupertype extends Supertype {",
            "  Bar bar();",
            "",
            "  @Subcomponent.Builder",
            "  interface Builder {",
            "    Supertype build();",
            "  }",
            "}");

    Compilation compilation = compile(foo, bar, supertype, subcomponent);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .hadWarningContaining(
            process(
                "test.HasSupertype.Builder.build() returns test.Supertype, but test.HasSupertype "
                    + "declares additional component method(s): bar(). In order to provide "
                    + "type-safe access to these methods, override build() to return "
                    + "test.HasSupertype"))
        .inFile(subcomponent)
        .onLine(11);
  }

  @Test
  public void covariantFactoryMethodReturnType_hasNewMethod_buildMethodInherited() {
    JavaFileObject foo =
        JavaFileObjects.forSourceLines(
            "test.Foo",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Foo {",
            "  @Inject Foo() {}",
            "}");
    JavaFileObject bar =
        JavaFileObjects.forSourceLines(
            "test.Bar",
            "package test;",
            "",
            "import javax.inject.Inject;",
            "",
            "class Bar {",
            "  @Inject Bar() {}",
            "}");
    JavaFileObject supertype =
        JavaFileObjects.forSourceLines(
            "test.Supertype",
            "package test;",
            "",
            "interface Supertype {",
            "  Foo foo();",
            "}");

    JavaFileObject creatorSupertype =
        preprocessedJavaFile(
            "test.CreatorSupertype",
            "package test;",
            "",
            "interface CreatorSupertype {",
            "  Supertype build();",
            "}");

    JavaFileObject subcomponent =
        preprocessedJavaFile(
            "test.HasSupertype",
            "package test;",
            "",
            "import dagger.Subcomponent;",
            "",
            "@Subcomponent",
            "interface HasSupertype extends Supertype {",
            "  Bar bar();",
            "",
            "  @Subcomponent.Builder",
            "  interface Builder extends CreatorSupertype {}",
            "}");

    Compilation compilation = compile(foo, bar, supertype, creatorSupertype, subcomponent);
    assertThat(compilation).succeeded();
    assertThat(compilation)
        .hadWarningContaining(
            process(
                "[test.CreatorSupertype.build()] test.HasSupertype.Builder.build() returns "
                    + "test.Supertype, but test.HasSupertype declares additional component "
                    + "method(s): bar(). In order to provide type-safe access to these methods, "
                    + "override build() to return test.HasSupertype"));
  }
}
