• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.hilt.android.internal.testing;
18 
19 import android.app.Application;
20 import dagger.hilt.android.testing.OnComponentReadyRunner;
21 import dagger.hilt.android.testing.OnComponentReadyRunner.OnComponentReadyRunnerHolder;
22 import dagger.hilt.internal.GeneratedComponentManager;
23 import dagger.hilt.internal.Preconditions;
24 import dagger.hilt.internal.TestSingletonComponentManager;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.atomic.AtomicReference;
30 import org.junit.runner.Description;
31 
32 /**
33  * Do not use except in Hilt generated code!
34  *
35  * <p>A manager for the creation of components that live in the test Application.
36  */
37 public final class TestApplicationComponentManager
38     implements TestSingletonComponentManager, OnComponentReadyRunnerHolder {
39 
40   private final Object earlyComponentLock = new Object();
41   private volatile Object earlyComponent = null;
42 
43   private final Object testComponentDataLock = new Object();
44   private volatile TestComponentData testComponentData;
45 
46   private final Application application;
47   private final AtomicReference<Object> component = new AtomicReference<>();
48   private final AtomicReference<Description> hasHiltTestRule = new AtomicReference<>();
49   // TODO(bcorso): Consider using a lock here rather than ConcurrentHashMap to avoid b/37042460.
50   private final Map<Class<?>, Object> registeredModules = new ConcurrentHashMap<>();
51   private final AtomicReference<Boolean> autoAddModuleEnabled = new AtomicReference<>();
52   private final AtomicReference<DelayedComponentState> delayedComponentState =
53       new AtomicReference<>(DelayedComponentState.NOT_DELAYED);
54   private volatile Object testInstance;
55   private volatile OnComponentReadyRunner onComponentReadyRunner = new OnComponentReadyRunner();
56 
57   /**
58    * Represents the state of Component readiness. There are two valid transition sequences.
59    *
60    * <ul>
61    *   <li>Typical test (no HiltAndroidRule#delayComponentReady): {@code NOT_DELAYED -> INJECTED}
62    *   <li>Using HiltAndroidRule#delayComponentReady: {@code NOT_DELAYED -> COMPONENT_DELAYED ->
63    *       COMPONENT_READY -> INJECTED}
64    * </ul>
65    */
66   private enum DelayedComponentState {
67     // Valid transitions: COMPONENT_DELAYED, INJECTED
68     NOT_DELAYED,
69     // Valid transitions: COMPONENT_READY
70     COMPONENT_DELAYED,
71     // Valid transitions: INJECTED
72     COMPONENT_READY,
73     // Terminal state
74     INJECTED
75   }
76 
TestApplicationComponentManager(Application application)77   public TestApplicationComponentManager(Application application) {
78     this.application = application;
79   }
80 
81   @Override
earlySingletonComponent()82   public Object earlySingletonComponent() {
83     if (earlyComponent == null) {
84       synchronized (earlyComponentLock) {
85         if (earlyComponent == null) {
86           earlyComponent = EarlySingletonComponentCreator.createComponent();
87         }
88       }
89     }
90     return earlyComponent;
91   }
92 
93   @Override
generatedComponent()94   public Object generatedComponent() {
95     if (component.get() == null) {
96       Preconditions.checkState(
97           hasHiltTestRule(),
98       "The component was not created. Check that you have added the HiltAndroidRule.");
99       if (!registeredModules.keySet().containsAll(requiredModules())) {
100         Set<Class<?>> difference = new HashSet<>(requiredModules());
101         difference.removeAll(registeredModules.keySet());
102         throw new IllegalStateException(
103             "The component was not created. Check that you have "
104                 + "registered all test modules:\n\tUnregistered: "
105                 + difference);
106       }
107       Preconditions.checkState(
108           bindValueReady(), "The test instance has not been set. Did you forget to call #bind()?");
109       throw new IllegalStateException(
110           "The component has not been created. "
111               + "Check that you have called #inject()? Otherwise, "
112               + "there is a race between injection and component creation. Make sure there is a "
113               + "happens-before edge between the HiltAndroidRule/registering"
114               + " all test modules and the first injection.");
115     }
116     return component.get();
117   }
118 
119   @Override
getOnComponentReadyRunner()120   public OnComponentReadyRunner getOnComponentReadyRunner() {
121     return onComponentReadyRunner;
122   }
123 
124   /** For framework use only! This flag must be set before component creation. */
setHasHiltTestRule(Description description)125   void setHasHiltTestRule(Description description) {
126     Preconditions.checkState(
127         // Some exempted tests set the test rule multiple times. Use CAS to avoid setting twice.
128         hasHiltTestRule.compareAndSet(null, description),
129         "The hasHiltTestRule flag has already been set!");
130     tryToCreateComponent();
131   }
132 
checkStateIsCleared()133   void checkStateIsCleared() {
134     Preconditions.checkState(
135         component.get() == null,
136         "The Hilt component cannot be set before Hilt's test rule has run.");
137     Preconditions.checkState(
138         hasHiltTestRule.get() == null,
139         "The Hilt test rule cannot be set before Hilt's test rule has run.");
140     Preconditions.checkState(
141         autoAddModuleEnabled.get() == null,
142         "The Hilt autoAddModuleEnabled cannot be set before Hilt's test rule has run.");
143     Preconditions.checkState(
144         testInstance == null,
145         "The Hilt BindValue instance cannot be set before Hilt's test rule has run.");
146     Preconditions.checkState(
147         testComponentData == null,
148         "The testComponentData instance cannot be set before Hilt's test rule has run.");
149     Preconditions.checkState(
150         registeredModules.isEmpty(),
151         "The Hilt registered modules cannot be set before Hilt's test rule has run.");
152     Preconditions.checkState(
153         onComponentReadyRunner.isEmpty(),
154         "The Hilt onComponentReadyRunner cannot add listeners before Hilt's test rule has run.");
155     DelayedComponentState state = delayedComponentState.get();
156     switch (state) {
157       case NOT_DELAYED:
158       case COMPONENT_DELAYED:
159         // Expected
160         break;
161       case COMPONENT_READY:
162         throw new IllegalStateException("Called componentReady before test execution started");
163       case INJECTED:
164         throw new IllegalStateException("Called inject before test execution started");
165     }
166   }
167 
clearState()168   void clearState() {
169     component.set(null);
170     hasHiltTestRule.set(null);
171     testInstance = null;
172     testComponentData = null;
173     registeredModules.clear();
174     autoAddModuleEnabled.set(null);
175     delayedComponentState.set(DelayedComponentState.NOT_DELAYED);
176     onComponentReadyRunner = new OnComponentReadyRunner();
177   }
178 
getDescription()179   public Description getDescription() {
180     return hasHiltTestRule.get();
181   }
182 
getTestInstance()183   public Object getTestInstance() {
184     Preconditions.checkState(
185         testInstance != null,
186         "The test instance has not been set.");
187     return testInstance;
188   }
189 
190   /** For framework use only! This method should be called when a required module is installed. */
registerModule(Class<T> moduleClass, T module)191   public <T> void registerModule(Class<T> moduleClass, T module) {
192     Preconditions.checkNotNull(moduleClass);
193     Preconditions.checkState(
194         testComponentData().daggerRequiredModules().contains(moduleClass),
195         "Found unknown module class: %s",
196         moduleClass.getName());
197     if (requiredModules().contains(moduleClass)) {
198       Preconditions.checkState(
199           // Some exempted tests register modules multiple times.
200           !registeredModules.containsKey(moduleClass),
201           "Module is already registered: %s",
202           moduleClass.getName());
203 
204       registeredModules.put(moduleClass, module);
205       tryToCreateComponent();
206     }
207   }
208 
delayComponentReady()209   void delayComponentReady() {
210     switch (delayedComponentState.getAndSet(DelayedComponentState.COMPONENT_DELAYED)) {
211       case NOT_DELAYED:
212         // Expected
213         break;
214       case COMPONENT_DELAYED:
215         throw new IllegalStateException("Called delayComponentReady() twice");
216       case COMPONENT_READY:
217         throw new IllegalStateException("Called delayComponentReady() after componentReady()");
218       case INJECTED:
219         throw new IllegalStateException("Called delayComponentReady() after inject()");
220     }
221   }
222 
componentReady()223   void componentReady() {
224     switch (delayedComponentState.getAndSet(DelayedComponentState.COMPONENT_READY)) {
225       case NOT_DELAYED:
226         throw new IllegalStateException(
227             "Called componentReady(), even though delayComponentReady() was not used.");
228       case COMPONENT_DELAYED:
229         // Expected
230         break;
231       case COMPONENT_READY:
232         throw new IllegalStateException("Called componentReady() multiple times");
233       case INJECTED:
234         throw new IllegalStateException("Called componentReady() after inject()");
235     }
236     tryToCreateComponent();
237   }
238 
inject()239   void inject() {
240     switch (delayedComponentState.getAndSet(DelayedComponentState.INJECTED)) {
241       case NOT_DELAYED:
242       case COMPONENT_READY:
243         // Expected
244         break;
245       case COMPONENT_DELAYED:
246         throw new IllegalStateException("Called inject() before calling componentReady()");
247       case INJECTED:
248         throw new IllegalStateException("Called inject() multiple times");
249     }
250     Preconditions.checkNotNull(testInstance);
251     testInjector().injectTest(testInstance);
252   }
253 
verifyDelayedComponentWasMadeReady()254   void verifyDelayedComponentWasMadeReady() {
255     Preconditions.checkState(
256         delayedComponentState.get() != DelayedComponentState.COMPONENT_DELAYED,
257         "Used delayComponentReady(), but never called componentReady()");
258   }
259 
tryToCreateComponent()260   private void tryToCreateComponent() {
261     if (hasHiltTestRule()
262         && registeredModules.keySet().containsAll(requiredModules())
263         && bindValueReady()
264         && delayedComponentReady()) {
265       Preconditions.checkState(
266           autoAddModuleEnabled.get() !=  null,
267           "Component cannot be created before autoAddModuleEnabled is set.");
268       Preconditions.checkState(
269           component.compareAndSet(
270               null,
271               componentSupplier().get(registeredModules, testInstance, autoAddModuleEnabled.get())),
272           "Tried to create the component more than once! "
273               + "There is a race between registering the HiltAndroidRule and registering"
274               + " all test modules. Make sure there is a happens-before edge between the two.");
275       onComponentReadyRunner.setComponentManager((GeneratedComponentManager) application);
276     }
277   }
278 
setTestInstance(Object testInstance)279   void setTestInstance(Object testInstance) {
280     Preconditions.checkNotNull(testInstance);
281     Preconditions.checkState(this.testInstance == null, "The test instance was already set!");
282     this.testInstance = testInstance;
283   }
284 
setAutoAddModule(boolean autoAddModule)285   void setAutoAddModule(boolean autoAddModule) {
286     Preconditions.checkState(
287         autoAddModuleEnabled.get() == null, "autoAddModuleEnabled is already set!");
288     autoAddModuleEnabled.set(autoAddModule);
289   }
290 
requiredModules()291   private Set<Class<?>> requiredModules() {
292     return autoAddModuleEnabled.get()
293         ? testComponentData().hiltRequiredModules()
294         : testComponentData().daggerRequiredModules();
295   }
296 
waitForBindValue()297   private boolean waitForBindValue() {
298     return testComponentData().waitForBindValue();
299   }
300 
testInjector()301   private TestInjector<Object> testInjector() {
302     return testComponentData().testInjector();
303   }
304 
componentSupplier()305   private TestComponentData.ComponentSupplier componentSupplier() {
306     return testComponentData().componentSupplier();
307   }
308 
testComponentData()309   private TestComponentData testComponentData() {
310     if (testComponentData == null) {
311       synchronized (testComponentDataLock) {
312         if (testComponentData == null) {
313           testComponentData = TestComponentDataSupplier.get(testClass());
314         }
315       }
316     }
317     return testComponentData;
318   }
319 
testClass()320   private Class<?> testClass() {
321     Preconditions.checkState(
322         hasHiltTestRule(),
323     "Test must have an HiltAndroidRule.");
324     return hasHiltTestRule.get().getTestClass();
325   }
326 
bindValueReady()327   private boolean bindValueReady() {
328     return !waitForBindValue() || testInstance != null;
329   }
330 
delayedComponentReady()331   private boolean delayedComponentReady() {
332     return delayedComponentState.get() != DelayedComponentState.COMPONENT_DELAYED;
333   }
334 
hasHiltTestRule()335   private boolean hasHiltTestRule() {
336     return hasHiltTestRule.get() != null;
337   }
338 }
339