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