/*
 * 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.compilerWithOptions;
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 ScopingValidationTest {
  @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(
                "MyComponent (unscoped) may not reference scoped bindings:",
                "    @Singleton class ScopedType",
                "    @Provides @Singleton String 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(
                "Parent scoped with @Singleton may not reference bindings with different "
                    + "scopes:",
                "    @Binds @ChildScope Foo ParentModule.bind(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(
                "MyComponent scoped with @Singleton "
                    + "may not reference bindings with different scopes:",
                "    @PerTest class ScopedType",
                "    @Provides @PerTest String ScopedModule.string()",
                "    @Provides @Per(MyComponent.class) boolean "
                    + "ScopedModule.bool()"))
        .inFile(componentFile)
        .onLineContaining("interface MyComponent");

    compilation =
        compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR")
            .compile(componentFile, scopeFile, scopeWithAttribute, typeFile, moduleFile);
    // The @Inject binding for ScopedType should not appear here, but the @Singleton binding should.
    assertThat(compilation)
        .hadErrorContaining(
            message(
                "ScopedModule contains bindings with different scopes:",
                "    @Provides @PerTest String ScopedModule.string()",
                "    @Provides @Singleton float ScopedModule.floatingPoint()",
                "    @Provides @Per(MyComponent.class) boolean "
                    + "ScopedModule.bool()"))
        .inFile(moduleFile)
        .onLineContaining("class ScopedModule");
  }

  @Test
  public void fullBindingGraphValidationDoesNotReportForOneScope() {
    Compilation compilation =
        compilerWithOptions(
                "-Adagger.fullBindingGraphValidation=ERROR",
                "-Adagger.moduleHasDifferentScopesValidation=ERROR")
            .compile(
                JavaFileObjects.forSourceLines(
                    "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; }",
                    "}"));
    assertThat(compilation).succeededWithoutWarnings();
  }

  @Test
  public void fullBindingGraphValidationDoesNotReportInjectBindings() {
    Compilation compilation =
        compilerWithOptions(
                "-Adagger.fullBindingGraphValidation=ERROR",
                "-Adagger.moduleHasDifferentScopesValidation=ERROR")
            .compile(
                JavaFileObjects.forSourceLines(
                    "test.UsedInRootRedScoped",
                    "package test;",
                    "",
                    "import javax.inject.Inject;",
                    "",
                    "@RedScope",
                    "final class UsedInRootRedScoped {",
                    "  @Inject UsedInRootRedScoped() {}",
                    "}"),
                JavaFileObjects.forSourceLines(
                    "test.UsedInRootBlueScoped",
                    "package test;",
                    "",
                    "import javax.inject.Inject;",
                    "",
                    "@BlueScope",
                    "final class UsedInRootBlueScoped {",
                    "  @Inject UsedInRootBlueScoped() {}",
                    "}"),
                JavaFileObjects.forSourceLines(
                    "test.RedScope",
                    "package test;",
                    "",
                    "import javax.inject.Scope;",
                    "",
                    "@Scope",
                    "@interface RedScope {}"),
                JavaFileObjects.forSourceLines(
                    "test.BlueScope",
                    "package test;",
                    "",
                    "import javax.inject.Scope;",
                    "",
                    "@Scope",
                    "@interface BlueScope {}"),
                JavaFileObjects.forSourceLines(
                    "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\";",
                    "  }",
                    "}"),
                JavaFileObjects.forSourceLines(
                    "test.Child",
                    "package test;",
                    "",
                    "import dagger.Subcomponent;",
                    "",
                    "@Subcomponent",
                    "interface Child {",
                    "  UsedInChildRedScoped usedInChildRedScoped();",
                    "  UsedInChildBlueScoped usedInChildBlueScoped();",
                    "",
                    "  @Subcomponent.Builder",
                    "  interface Builder {",
                    "    Child child();",
                    "  }",
                    "}"),
                JavaFileObjects.forSourceLines(
                    "test.UsedInChildRedScoped",
                    "package test;",
                    "",
                    "import javax.inject.Inject;",
                    "",
                    "@RedScope",
                    "final class UsedInChildRedScoped {",
                    "  @Inject UsedInChildRedScoped() {}",
                    "}"),
                JavaFileObjects.forSourceLines(
                    "test.UsedInChildBlueScoped",
                    "package test;",
                    "",
                    "import javax.inject.Inject;",
                    "",
                    "@BlueScope",
                    "final class UsedInChildBlueScoped {",
                    "  @Inject UsedInChildBlueScoped() {}",
                    "}"));
    assertThat(compilation).succeededWithoutWarnings();
  }

  @Test
  public void componentWithScopeCanDependOnMultipleScopedComponents() {
    // If a scoped component will have dependencies, they can include multiple 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).succeededWithoutWarnings();
  }



  // Tests the following component hierarchy:
  //
  //        @ScopeA
  //        ComponentA
  //        [SimpleType getSimpleType()]
  //        /        \
  //       /          \
  //   @ScopeB         @ScopeB
  //   ComponentB1     ComponentB2
  //      \            [SimpleType getSimpleType()]
  //       \          /
  //        \        /
  //         @ScopeC
  //         ComponentC
  //         [SimpleType getSimpleType()]
  @Test
  public void componentWithScopeCanDependOnMultipleScopedComponentsEvenDoingADiamond() {
    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 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 componentA =
        JavaFileObjects.forSourceLines(
            "test.ComponentA",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeA",
            "@Component",
            "interface ComponentA {",
            "  SimpleType type();",
            "}");
    JavaFileObject componentB1 =
        JavaFileObjects.forSourceLines(
            "test.ComponentB1",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeB",
            "@Component(dependencies = ComponentA.class)",
            "interface ComponentB1 {",
            "  SimpleType type();",
            "}");
    JavaFileObject componentB2 =
        JavaFileObjects.forSourceLines(
            "test.ComponentB2",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeB",
            "@Component(dependencies = ComponentA.class)",
            "interface ComponentB2 {",
            "}");
    JavaFileObject componentC =
        JavaFileObjects.forSourceLines(
            "test.ComponentC",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@SimpleScope",
            "@Component(dependencies = {ComponentB1.class, ComponentB2.class})",
            "interface ComponentC {",
            "  SimpleType type();",
            "}");

    Compilation compilation =
        daggerCompiler()
            .compile(
                type, simpleScope, scopeA, scopeB,
                componentA, componentB1, componentB2, componentC);
    assertThat(compilation).succeededWithoutWarnings();
  }

  @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 componentScopeWithMultipleScopedDependenciesMustNotCycle() {
    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 mediumLifetime1 =
        JavaFileObjects.forSourceLines(
            "test.ComponentMedium1",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeB",
            "@Component(dependencies = ComponentLong.class)",
            "interface ComponentMedium1 {",
            "  SimpleType type();",
            "}");
    JavaFileObject mediumLifetime2 =
        JavaFileObjects.forSourceLines(
            "test.ComponentMedium2",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeB",
            "@Component",
            "interface ComponentMedium2 {",
            "}");
    JavaFileObject shortLifetime =
        JavaFileObjects.forSourceLines(
            "test.ComponentShort",
            "package test;",
            "",
            "import dagger.Component;",
            "",
            "@ScopeA",
            "@Component(dependencies = {ComponentMedium1.class, ComponentMedium2.class})",
            "interface ComponentShort {",
            "  SimpleType type();",
            "}");

    Compilation compilation =
        daggerCompiler()
            .compile(
                type, scopeA, scopeB,
                longLifetime, mediumLifetime1, mediumLifetime2, 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.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.
    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 that compilation succeeds when transitive validation is disabled because the scope cycle
    // cannot be detected.
    compilation =
        compilerWithOptions("-Adagger.validateTransitiveComponentDependencies=DISABLED")
            .compile(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime);
    assertThat(compilation).succeeded();
  }

  @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);
  }
}
