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 java.lang.reflect.InvocationTargetException; 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 GeneratedComponentManager<Object>, OnComponentReadyRunnerHolder { 39 40 // This is a generated class that we always generate in a known location. 41 private static final String TEST_COMPONENT_DATA_SUPPLIER_IMPL = 42 "dagger.hilt.android.internal.testing.TestComponentDataSupplierImpl"; 43 44 private final Application application; 45 private final Map<Class<?>, TestComponentData> testComponentDataSupplier; 46 47 private final AtomicReference<Object> component = new AtomicReference<>(); 48 private final AtomicReference<Description> hasHiltTestRule = new AtomicReference<>(); 49 private final Map<Class<?>, Object> registeredModules = new ConcurrentHashMap<>(); 50 private final AtomicReference<Boolean> autoAddModuleEnabled = new AtomicReference<>(); 51 private final AtomicReference<DelayedComponentState> delayedComponentState = 52 new AtomicReference<>(DelayedComponentState.NOT_DELAYED); 53 private volatile Object testInstance; 54 private volatile OnComponentReadyRunner onComponentReadyRunner = new OnComponentReadyRunner(); 55 56 /** 57 * Represents the state of Component readiness. There are two valid transition sequences. 58 * 59 * <ul> 60 * <li>Typical test (no HiltAndroidRule#delayComponentReady): {@code NOT_DELAYED -> INJECTED} 61 * <li>Using HiltAndroidRule#delayComponentReady: {@code NOT_DELAYED -> COMPONENT_DELAYED -> 62 * COMPONENT_READY -> INJECTED} 63 * </ul> 64 */ 65 private enum DelayedComponentState { 66 // Valid transitions: COMPONENT_DELAYED, INJECTED 67 NOT_DELAYED, 68 // Valid transitions: COMPONENT_READY 69 COMPONENT_DELAYED, 70 // Valid transitions: INJECTED 71 COMPONENT_READY, 72 // Terminal state 73 INJECTED 74 } 75 TestApplicationComponentManager(Application application)76 public TestApplicationComponentManager(Application application) { 77 this.application = application; 78 try { 79 this.testComponentDataSupplier = 80 Class.forName(TEST_COMPONENT_DATA_SUPPLIER_IMPL) 81 .asSubclass(TestComponentDataSupplier.class) 82 .getDeclaredConstructor() 83 .newInstance() 84 .get(); 85 } catch (ClassNotFoundException 86 | NoSuchMethodException 87 | IllegalAccessException 88 | InstantiationException 89 | InvocationTargetException e) { 90 throw new RuntimeException( 91 "Hilt classes generated from @HiltAndroidTest are missing. Check that you have annotated " 92 + "your test class with @HiltAndroidTest and that the processor is running over your " 93 + "test", 94 e); 95 } 96 } 97 98 @Override generatedComponent()99 public Object generatedComponent() { 100 if (component.get() == null) { 101 Preconditions.checkState( 102 hasHiltTestRule(), 103 "The component was not created. Check that you have added the HiltAndroidRule."); 104 if (!registeredModules.keySet().containsAll(requiredModules())) { 105 Set<Class<?>> difference = new HashSet<>(requiredModules()); 106 difference.removeAll(registeredModules.keySet()); 107 throw new IllegalStateException( 108 "The component was not created. Check that you have " 109 + "registered all test modules:\n\tUnregistered: " 110 + difference); 111 } 112 Preconditions.checkState( 113 bindValueReady(), "The test instance has not been set. Did you forget to call #bind()?"); 114 throw new IllegalStateException( 115 "The component has not been created. " 116 + "Check that you have called #inject()? Otherwise, " 117 + "there is a race between injection and component creation. Make sure there is a " 118 + "happens-before edge between the HiltAndroidRule/registering" 119 + " all test modules and the first injection."); 120 } 121 return component.get(); 122 } 123 124 @Override getOnComponentReadyRunner()125 public OnComponentReadyRunner getOnComponentReadyRunner() { 126 return onComponentReadyRunner; 127 } 128 129 /** For framework use only! This flag must be set before component creation. */ setHasHiltTestRule(Description description)130 void setHasHiltTestRule(Description description) { 131 Preconditions.checkState( 132 // Some exempted tests set the test rule multiple times. Use CAS to avoid setting twice. 133 hasHiltTestRule.compareAndSet(null, description), 134 "The hasHiltTestRule flag has already been set!"); 135 tryToCreateComponent(); 136 } 137 checkStateIsCleared()138 void checkStateIsCleared() { 139 Preconditions.checkState( 140 component.get() == null, 141 "The Hilt component cannot be set before Hilt's test rule has run."); 142 Preconditions.checkState( 143 hasHiltTestRule.get() == null, 144 "The Hilt test rule cannot be set before Hilt's test rule has run."); 145 Preconditions.checkState( 146 autoAddModuleEnabled.get() == null, 147 "The Hilt autoAddModuleEnabled cannot be set before Hilt's test rule has run."); 148 Preconditions.checkState( 149 testInstance == null, 150 "The Hilt BindValue instance cannot be set before Hilt's test rule has run."); 151 Preconditions.checkState( 152 registeredModules.isEmpty(), 153 "The Hilt registered modules cannot be set before Hilt's test rule has run."); 154 Preconditions.checkState( 155 onComponentReadyRunner.isEmpty(), 156 "The Hilt onComponentReadyRunner cannot add listeners before Hilt's test rule has run."); 157 DelayedComponentState state = delayedComponentState.get(); 158 switch (state) { 159 case NOT_DELAYED: 160 case COMPONENT_DELAYED: 161 // Expected 162 break; 163 case COMPONENT_READY: 164 throw new IllegalStateException("Called componentReady before test execution started"); 165 case INJECTED: 166 throw new IllegalStateException("Called inject before test execution started"); 167 } 168 } 169 clearState()170 void clearState() { 171 component.set(null); 172 hasHiltTestRule.set(null); 173 testInstance = null; 174 registeredModules.clear(); 175 autoAddModuleEnabled.set(null); 176 delayedComponentState.set(DelayedComponentState.NOT_DELAYED); 177 onComponentReadyRunner = new OnComponentReadyRunner(); 178 } 179 getDescription()180 public Description getDescription() { 181 return hasHiltTestRule.get(); 182 } 183 getTestInstance()184 public Object getTestInstance() { 185 Preconditions.checkState( 186 testInstance != null, 187 "The test instance has not been set."); 188 return testInstance; 189 } 190 191 /** For framework use only! This method should be called when a required module is installed. */ registerModule(Class<T> moduleClass, T module)192 public <T> void registerModule(Class<T> moduleClass, T module) { 193 Preconditions.checkNotNull(moduleClass); 194 Preconditions.checkState( 195 testComponentData().daggerRequiredModules().contains(moduleClass), 196 "Found unknown module class: %s", 197 moduleClass.getName()); 198 if (requiredModules().contains(moduleClass)) { 199 Preconditions.checkState( 200 // Some exempted tests register modules multiple times. 201 !registeredModules.containsKey(moduleClass), 202 "Module is already registered: %s", 203 moduleClass.getName()); 204 205 registeredModules.put(moduleClass, module); 206 tryToCreateComponent(); 207 } 208 } 209 delayComponentReady()210 void delayComponentReady() { 211 switch (delayedComponentState.getAndSet(DelayedComponentState.COMPONENT_DELAYED)) { 212 case NOT_DELAYED: 213 // Expected 214 break; 215 case COMPONENT_DELAYED: 216 throw new IllegalStateException("Called delayComponentReady() twice"); 217 case COMPONENT_READY: 218 throw new IllegalStateException("Called delayComponentReady() after componentReady()"); 219 case INJECTED: 220 throw new IllegalStateException("Called delayComponentReady() after inject()"); 221 } 222 } 223 componentReady()224 void componentReady() { 225 switch (delayedComponentState.getAndSet(DelayedComponentState.COMPONENT_READY)) { 226 case NOT_DELAYED: 227 throw new IllegalStateException( 228 "Called componentReady(), even though delayComponentReady() was not used."); 229 case COMPONENT_DELAYED: 230 // Expected 231 break; 232 case COMPONENT_READY: 233 throw new IllegalStateException("Called componentReady() multiple times"); 234 case INJECTED: 235 throw new IllegalStateException("Called componentReady() after inject()"); 236 } 237 tryToCreateComponent(); 238 } 239 inject()240 void inject() { 241 switch (delayedComponentState.getAndSet(DelayedComponentState.INJECTED)) { 242 case NOT_DELAYED: 243 case COMPONENT_READY: 244 // Expected 245 break; 246 case COMPONENT_DELAYED: 247 throw new IllegalStateException("Called inject() before calling componentReady()"); 248 case INJECTED: 249 throw new IllegalStateException("Called inject() multiple times"); 250 } 251 Preconditions.checkNotNull(testInstance); 252 testInjector().injectTest(testInstance); 253 } 254 verifyDelayedComponentWasMadeReady()255 void verifyDelayedComponentWasMadeReady() { 256 Preconditions.checkState( 257 delayedComponentState.get() != DelayedComponentState.COMPONENT_DELAYED, 258 "Used delayComponentReady(), but never called componentReady()"); 259 } 260 tryToCreateComponent()261 private void tryToCreateComponent() { 262 if (hasHiltTestRule() 263 && registeredModules.keySet().containsAll(requiredModules()) 264 && bindValueReady() 265 && delayedComponentReady()) { 266 Preconditions.checkState( 267 autoAddModuleEnabled.get() != null, 268 "Component cannot be created before autoAddModuleEnabled is set."); 269 Preconditions.checkState( 270 component.compareAndSet( 271 null, 272 componentSupplier().get(registeredModules, testInstance, autoAddModuleEnabled.get())), 273 "Tried to create the component more than once! " 274 + "There is a race between registering the HiltAndroidRule and registering" 275 + " all test modules. Make sure there is a happens-before edge between the two."); 276 onComponentReadyRunner.setComponentManager((GeneratedComponentManager) application); 277 } 278 } 279 setTestInstance(Object testInstance)280 void setTestInstance(Object testInstance) { 281 Preconditions.checkNotNull(testInstance); 282 Preconditions.checkState(this.testInstance == null, "The test instance was already set!"); 283 this.testInstance = testInstance; 284 } 285 setAutoAddModule(boolean autoAddModule)286 void setAutoAddModule(boolean autoAddModule) { 287 Preconditions.checkState( 288 autoAddModuleEnabled.get() == null, "autoAddModuleEnabled is already set!"); 289 autoAddModuleEnabled.set(autoAddModule); 290 } 291 requiredModules()292 private Set<Class<?>> requiredModules() { 293 return autoAddModuleEnabled.get() 294 ? testComponentData().hiltRequiredModules() 295 : testComponentData().daggerRequiredModules(); 296 } 297 waitForBindValue()298 private boolean waitForBindValue() { 299 return testComponentData().waitForBindValue(); 300 } 301 testInjector()302 private TestInjector<Object> testInjector() { 303 return testComponentData().testInjector(); 304 } 305 componentSupplier()306 private TestComponentData.ComponentSupplier componentSupplier() { 307 return testComponentData().componentSupplier(); 308 } 309 testComponentData()310 private TestComponentData testComponentData() { 311 return testComponentDataSupplier.get(testClass()); 312 } 313 testClass()314 private Class<?> testClass() { 315 Preconditions.checkState( 316 hasHiltTestRule(), 317 "Test must have an HiltAndroidRule."); 318 return hasHiltTestRule.get().getTestClass(); 319 } 320 bindValueReady()321 private boolean bindValueReady() { 322 return !waitForBindValue() || testInstance != null; 323 } 324 delayedComponentReady()325 private boolean delayedComponentReady() { 326 return delayedComponentState.get() != DelayedComponentState.COMPONENT_DELAYED; 327 } 328 hasHiltTestRule()329 private boolean hasHiltTestRule() { 330 return hasHiltTestRule.get() != null; 331 } 332 } 333