1 /* 2 * Copyright 2018 The Android Open Source Project 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 androidx.work.testing; 18 19 import static androidx.work.testing.TestWorkManagerImplKt.createTestWorkManagerImpl; 20 import static androidx.work.testing.WorkManagerTestInitHelper.ExecutorsMode.LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS; 21 22 import android.content.Context; 23 24 import androidx.work.Configuration; 25 import androidx.work.impl.WorkManagerImpl; 26 import androidx.work.impl.utils.SerialExecutorImpl; 27 import androidx.work.impl.utils.taskexecutor.SerialExecutor; 28 29 import org.jspecify.annotations.NonNull; 30 import org.jspecify.annotations.Nullable; 31 32 import java.util.UUID; 33 34 /** 35 * Helps initialize {@link androidx.work.WorkManager} for testing. 36 */ 37 public final class WorkManagerTestInitHelper { 38 /** 39 * Initializes a test {@link androidx.work.WorkManager} with a {@link SynchronousExecutor}. 40 * 41 * @param context The application {@link Context} 42 */ initializeTestWorkManager(@onNull Context context)43 public static void initializeTestWorkManager(@NonNull Context context) { 44 SynchronousExecutor synchronousExecutor = new SynchronousExecutor(); 45 Configuration configuration = new Configuration.Builder() 46 .setExecutor(synchronousExecutor) 47 .setTaskExecutor(synchronousExecutor) 48 .build(); 49 initializeTestWorkManager(context, configuration); 50 } 51 52 /** 53 * Initializes a test {@link androidx.work.WorkManager} with a user-specified 54 * {@link androidx.work.Configuration}, but using 55 * {@link SynchronousExecutor} instead of main thread. 56 * 57 * @param context The application {@link Context} 58 * @param configuration The {@link androidx.work.Configuration} 59 */ initializeTestWorkManager( @onNull Context context, @NonNull Configuration configuration)60 public static void initializeTestWorkManager( 61 @NonNull Context context, 62 @NonNull Configuration configuration) { 63 initializeTestWorkManager(context, configuration, 64 LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS); 65 } 66 67 /** 68 * Modes that control which executors are used in tests. 69 */ 70 public enum ExecutorsMode { 71 /** 72 * Use executors as they are configured in passed {@link Configuration} and preserving 73 * real main thread. 74 */ 75 PRESERVE_EXECUTORS, 76 77 /** 78 * Preserve old behavior of {@link #initializeTestWorkManager(Context)} and 79 * {@link #initializeTestWorkManager(Context, Configuration)}. 80 * 81 * <p> In this mode {@link SynchronousExecutor} is used instead of main thread. Similarly, 82 * {@link SynchronousExecutor} is used as {@link Configuration#getTaskExecutor()}, unless 83 * {@link Configuration#getTaskExecutor()} was explicitly set in {@code configuration} 84 * passed in {@link #initializeTestWorkManager(Context, Configuration, ExecutorsMode)} 85 */ 86 LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS, 87 88 /** 89 * Like {@link #PRESERVE_EXECUTORS}, but uses the real Clock and RunnableScheduler in 90 * the provided {@link Configuration} instead of the {@link TestDriver} setDelayMet() 91 * methods to run scheduled work. 92 * 93 * <p> Work will be passed to RunnableScheduler with appropriate time-based delay, and the 94 * RunnableScheduler must reschedule the work itself when the clock delay has passed. 95 * 96 * {@link TestDriver#setInitialDelayMet(UUID)} and 97 * {@link TestDriver#setPeriodDelayMet(UUID)} 98 * throw exceptions when this configuration is used. 99 * 100 * <p> This mode is intended for integrated fake clock / schedule test frameworks, eg. 101 * {@link kotlinx.coroutines.test.StandardTestDispatcherImpl} with 102 * {@link kotlinx.coroutines.test.TestCoroutineScheduler} 103 */ 104 USE_TIME_BASED_SCHEDULING, 105 } 106 107 /** 108 * Initializes a test {@link androidx.work.WorkManager} that can be controlled via {@link 109 * TestDriver}. 110 * 111 * @param context The application {@link Context} 112 * @param configuration test configuration of WorkManager 113 * @param executorsMode mode controlling executors used by WorkManager in tests. See 114 * documentation of modes in {@link ExecutorsMode} 115 */ initializeTestWorkManager(@onNull Context context, @NonNull Configuration configuration, @NonNull ExecutorsMode executorsMode)116 public static void initializeTestWorkManager(@NonNull Context context, 117 @NonNull Configuration configuration, @NonNull ExecutorsMode executorsMode) { 118 WorkManagerImpl workManager; 119 switch (executorsMode) { 120 case LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS: 121 SerialExecutor serialExecutor; 122 if (configuration.isUsingDefaultTaskExecutor()) { 123 Configuration.Builder builder = new Configuration.Builder(configuration) 124 .setTaskExecutor(new SynchronousExecutor()); 125 configuration = builder.build(); 126 serialExecutor = new SynchronousSerialExecutor(); 127 } else { 128 serialExecutor = new SerialExecutorImpl(configuration.getTaskExecutor()); 129 } 130 workManager = createTestWorkManagerImpl( 131 context, configuration, serialExecutor, executorsMode); 132 break; 133 case PRESERVE_EXECUTORS: 134 case USE_TIME_BASED_SCHEDULING: 135 workManager = createTestWorkManagerImpl( 136 context, configuration, executorsMode); 137 break; 138 default: 139 throw new IllegalStateException("Unexpected value: " + executorsMode); 140 } 141 WorkManagerImpl.setDelegate(workManager); 142 } 143 144 /** 145 * Initializes a test {@link androidx.work.WorkManager} that can be controlled via {@link 146 * TestDriver}. 147 * 148 * @param context The application {@link Context} 149 * @param executorsMode mode controlling executors used by WorkManager in tests. See 150 * documentation of modes in {@link ExecutorsMode} 151 */ initializeTestWorkManager(@onNull Context context, @NonNull ExecutorsMode executorsMode)152 public static void initializeTestWorkManager(@NonNull Context context, 153 @NonNull ExecutorsMode executorsMode) { 154 Configuration configuration; 155 if (executorsMode == LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS) { 156 SynchronousExecutor synchronousExecutor = new SynchronousExecutor(); 157 configuration = new Configuration.Builder() 158 .setExecutor(synchronousExecutor) 159 .setTaskExecutor(synchronousExecutor) 160 .build(); 161 } else { 162 configuration = new Configuration.Builder().build(); 163 } 164 initializeTestWorkManager(context, configuration, executorsMode); 165 } 166 167 /** 168 * @return An instance of {@link TestDriver}. This exposes additional functionality that is 169 * useful in the context of testing when using WorkManager. 170 * @deprecated Call {@link WorkManagerTestInitHelper#getTestDriver(Context)} instead. 171 */ 172 @Deprecated getTestDriver()173 public static @Nullable TestDriver getTestDriver() { 174 WorkManagerImpl workManager = WorkManagerImpl.getInstance(); 175 if (workManager == null) { 176 return null; 177 } else { 178 return TestWorkManagerImplKt.getTestDriver(workManager); 179 } 180 } 181 182 /** 183 * @return An instance of {@link TestDriver}. This exposes additional functionality that is 184 * useful in the context of testing when using WorkManager. 185 */ getTestDriver(@onNull Context context)186 public static @Nullable TestDriver getTestDriver(@NonNull Context context) { 187 try { 188 return TestWorkManagerImplKt.getTestDriver(WorkManagerImpl.getInstance(context)); 189 } catch (IllegalStateException e) { 190 return null; 191 } 192 } 193 194 /** 195 * Closes internal {@link androidx.work.WorkManager}'s database. 196 * <p> 197 * It could be helpful to avoid warnings by CloseGuard in testing infra. You need to be 198 * make sure that {@code WorkManager} finished all operations and won't touch database 199 * anymore. Meaning that both {@link Configuration#getTaskExecutor()} and 200 * {@link Configuration#getExecutor()} are idle. 201 * <p> 202 * It shouldn't be called from {@link Configuration#getTaskExecutor()} 203 * because this method will block until all internal work is complete after 204 * cancellation. To complete this work {@link Configuration#getTaskExecutor()} 205 * could be require and blocking it may lead to deadlocks. 206 */ 207 @SuppressWarnings("deprecation") closeWorkDatabase()208 public static void closeWorkDatabase() { 209 WorkManagerImpl workManager = WorkManagerImpl.getInstance(); 210 if (workManager != null) { 211 workManager.closeDatabase(); 212 } 213 } 214 WorkManagerTestInitHelper()215 private WorkManagerTestInitHelper() { 216 } 217 } 218