/* * 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.functional.subcomponent; import static com.google.common.truth.Truth.assertThat; import dagger.Component; import dagger.Module; import dagger.Provides; import dagger.Subcomponent; import dagger.multibindings.IntoSet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; import javax.inject.Inject; import javax.inject.Qualifier; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Regression tests for an issue where subcomponent builder bindings were incorrectly reused from * a parent even if the subcomponent were redeclared on the child component. This manifested via * multibindings, especially since subcomponent builder bindings are special in that we cannot * traverse them to see if they depend on local multibinding contributions. */ @RunWith(JUnit4.class) public final class SubcomponentBuilderMultibindingsTest { @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface ParentFoo{} @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface ChildFoo{} public static final class Foo { final Set multi; @Inject Foo(Set multi) { this.multi = multi; } } // This tests the case where a subcomponent is installed in both the parent and child component. // In this case, we expect two subcomponents to be generated with the child one including the // child multibinding contribution. public static final class ChildInstallsFloating { @Component(modules = ParentModule.class) public interface Parent { @ParentFoo Foo getParentFoo(); Child getChild(); } @Subcomponent(modules = ChildModule.class) public interface Child { @ChildFoo Foo getChildFoo(); } @Subcomponent public interface FloatingSub { Foo getFoo(); @Subcomponent.Builder public interface Builder { FloatingSub build(); } } @Module(subcomponents = FloatingSub.class) public interface ParentModule { @Provides @IntoSet static String provideStringMulti() { return "parent"; } @Provides @ParentFoo static Foo provideParentFoo(FloatingSub.Builder builder) { return builder.build().getFoo(); } } // The subcomponent installation of FloatingSub here is the key difference @Module(subcomponents = FloatingSub.class) public interface ChildModule { @Provides @IntoSet static String provideStringMulti() { return "child"; } @Provides @ChildFoo static Foo provideChildFoo(FloatingSub.Builder builder) { return builder.build().getFoo(); } } private ChildInstallsFloating() {} } // This is the same as the above, except this time the child does not install the subcomponent // builder. Here, we expect the child to reuse the parent subcomponent binding (we want to avoid // any mistakes that might implicitly create a new subcomponent relationship) and so therefore // we expect only one subcomponent to be generated in the parent resulting in the child not seeing // the child multibinding contribution. public static final class ChildDoesNotInstallFloating { @Component(modules = ParentModule.class) public interface Parent { @ParentFoo Foo getParentFoo(); Child getChild(); } @Subcomponent(modules = ChildModule.class) public interface Child { @ChildFoo Foo getChildFoo(); } @Subcomponent public interface FloatingSub { Foo getFoo(); @Subcomponent.Builder public interface Builder { FloatingSub build(); } } @Module(subcomponents = FloatingSub.class) public interface ParentModule { @Provides @IntoSet static String provideStringMulti() { return "parent"; } @Provides @ParentFoo static Foo provideParentFoo(FloatingSub.Builder builder) { return builder.build().getFoo(); } } // The lack of a subcomponent installation of FloatingSub here is the key difference @Module public interface ChildModule { @Provides @IntoSet static String provideStringMulti() { return "child"; } @Provides @ChildFoo static Foo provideChildFoo(FloatingSub.Builder builder) { return builder.build().getFoo(); } } private ChildDoesNotInstallFloating() {} } // This is similar to the first, except this time the components installs the subcomponent via // factory methods. Here, we expect the child to get a new subcomponent and so should see its // multibinding contribution. public static final class ChildInstallsFloatingFactoryMethod { @Component(modules = ParentModule.class) public interface Parent { @ParentFoo Foo getParentFoo(); Child getChild(); FloatingSub getFloatingSub(); } @Subcomponent(modules = ChildModule.class) public interface Child { @ChildFoo Foo getChildFoo(); FloatingSub getFloatingSub(); } @Subcomponent public interface FloatingSub { Foo getFoo(); } @Module public interface ParentModule { @Provides @IntoSet static String provideStringMulti() { return "parent"; } @Provides @ParentFoo static Foo provideParentFoo(Parent componentSelf) { return componentSelf.getFloatingSub().getFoo(); } } @Module public interface ChildModule { @Provides @IntoSet static String provideStringMulti() { return "child"; } @Provides @ChildFoo static Foo provideChildFoo(Child componentSelf) { return componentSelf.getFloatingSub().getFoo(); } } private ChildInstallsFloatingFactoryMethod() {} } @Test public void testChildInstallsFloating() { ChildInstallsFloating.Parent parentComponent = DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloating_Parent.create(); assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child"); } @Test public void testChildDoesNotInstallFloating() { ChildDoesNotInstallFloating.Parent parentComponent = DaggerSubcomponentBuilderMultibindingsTest_ChildDoesNotInstallFloating_Parent.create(); assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); // Don't expect the child contribution because the child didn't redeclare the subcomponent // dependency, meaning it intends to just use the subcomponent relationship from the parent // component. assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent"); } @Test public void testChildInstallsFloatingFactoryMethod() { ChildInstallsFloatingFactoryMethod.Parent parentComponent = DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloatingFactoryMethod_Parent.create(); assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child"); } }