forked from square/dagger
-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix an issue where subcomponent builder bindings could be accidentall…
…y cached from the parent component. RELNOTES=Fix an issue with cached subcomponent builder bindings PiperOrigin-RevId: 360513850
- Loading branch information
1 parent
2192014
commit c2d097f
Showing
4 changed files
with
306 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
265 changes: 265 additions & 0 deletions
265
javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
/* | ||
* 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<String> multi; | ||
@Inject Foo(Set<String> 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"); | ||
} | ||
} |