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