WorkManagerTestInitHelper.java

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.work.testing;

import static androidx.work.testing.TestWorkManagerImplKt.createTestWorkManagerImpl;
import static androidx.work.testing.WorkManagerTestInitHelper.ExecutorsMode.LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.work.Configuration;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.utils.SerialExecutorImpl;
import androidx.work.impl.utils.taskexecutor.SerialExecutor;

import java.util.UUID;


/**
 * Helps initialize {@link androidx.work.WorkManager} for testing.
 */
public final class WorkManagerTestInitHelper {
    /**
     * Initializes a test {@link androidx.work.WorkManager} with a {@link SynchronousExecutor}.
     *
     * @param context The application {@link Context}
     */
    public static void initializeTestWorkManager(@NonNull Context context) {
        SynchronousExecutor synchronousExecutor = new SynchronousExecutor();
        Configuration configuration = new Configuration.Builder()
                .setExecutor(synchronousExecutor)
                .setTaskExecutor(synchronousExecutor)
                .build();
        initializeTestWorkManager(context, configuration);
    }

    /**
     * Initializes a test {@link androidx.work.WorkManager} with a user-specified
     * {@link androidx.work.Configuration}, but using
     * {@link SynchronousExecutor} instead of main thread.
     *
     * @param context       The application {@link Context}
     * @param configuration The {@link androidx.work.Configuration}
     */
    public static void initializeTestWorkManager(
            @NonNull Context context,
            @NonNull Configuration configuration) {
        initializeTestWorkManager(context, configuration,
                LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS);
    }

    /**
     * Modes that control which executors are used in tests.
     */
    public enum ExecutorsMode {
        /**
         * Use executors as they are configured in passed {@link Configuration} and preserving
         * real main thread.
         */
        PRESERVE_EXECUTORS,

        /**
         * Preserve old behavior of {@link #initializeTestWorkManager(Context)} and
         * {@link #initializeTestWorkManager(Context, Configuration)}.
         *
         * <p> In this mode {@link SynchronousExecutor} is used instead of main thread. Similarly,
         * {@link SynchronousExecutor} is used as {@link Configuration#getTaskExecutor()}, unless
         * {@link Configuration#getTaskExecutor()} was explicitly set in {@code configuration}
         * passed in {@link #initializeTestWorkManager(Context, Configuration, ExecutorsMode)}
         */
        LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS,

        /**
         * Like {@link #PRESERVE_EXECUTORS}, but uses the real Clock and RunnableScheduler in
         * the provided {@link Configuration} instead of the {@link TestDriver} setDelayMet()
         * methods to run scheduled work.
         *
         * <p> Work will be passed to RunnableScheduler with appropriate time-based delay, and the
         * RunnableScheduler must reschedule the work itself when the clock delay has passed.
         *
         * {@link TestDriver#setInitialDelayMet(UUID)} and
         * {@link TestDriver#setPeriodDelayMet(UUID)}
         * throw exceptions when this configuration is used.
         *
         * <p> This mode is intended for integrated fake clock / schedule test frameworks, eg.
         * {@link kotlinx.coroutines.test.StandardTestDispatcherImpl} with
         * {@link kotlinx.coroutines.test.TestCoroutineScheduler}
         */
        USE_TIME_BASED_SCHEDULING,
    }

    /**
     * Initializes a test {@link androidx.work.WorkManager} that can be controlled via {@link
     * TestDriver}.
     *
     * @param context       The application {@link Context}
     * @param configuration test configuration of WorkManager
     * @param executorsMode mode controlling executors used by WorkManager in tests. See
     *                      documentation of modes in {@link ExecutorsMode}
     */
    public static void initializeTestWorkManager(@NonNull Context context,
            @NonNull Configuration configuration, @NonNull ExecutorsMode executorsMode) {
        WorkManagerImpl workManager;
        switch (executorsMode) {
            case LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS:
                SerialExecutor serialExecutor;
                if (configuration.isUsingDefaultTaskExecutor()) {
                    Configuration.Builder builder = new Configuration.Builder(configuration)
                            .setTaskExecutor(new SynchronousExecutor());
                    configuration = builder.build();
                    serialExecutor = new SynchronousSerialExecutor();
                } else {
                    serialExecutor = new SerialExecutorImpl(configuration.getTaskExecutor());
                }
                workManager = createTestWorkManagerImpl(
                        context, configuration, serialExecutor, executorsMode);
                break;
            case PRESERVE_EXECUTORS:
            case USE_TIME_BASED_SCHEDULING:
                workManager = createTestWorkManagerImpl(
                        context, configuration, executorsMode);
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + executorsMode);
        }
        WorkManagerImpl.setDelegate(workManager);
    }

    /**
     * Initializes a test {@link androidx.work.WorkManager} that can be controlled via {@link
     * TestDriver}.
     *
     * @param context       The application {@link Context}
     * @param executorsMode mode controlling executors used by WorkManager in tests. See
     *                      documentation of modes in {@link ExecutorsMode}
     */
    public static void initializeTestWorkManager(@NonNull Context context,
            @NonNull ExecutorsMode executorsMode) {
        Configuration configuration;
        if (executorsMode == LEGACY_OVERRIDE_WITH_SYNCHRONOUS_EXECUTORS) {
            SynchronousExecutor synchronousExecutor = new SynchronousExecutor();
            configuration = new Configuration.Builder()
                    .setExecutor(synchronousExecutor)
                    .setTaskExecutor(synchronousExecutor)
                    .build();
        } else {
            configuration = new Configuration.Builder().build();
        }
        initializeTestWorkManager(context, configuration, executorsMode);
    }

    /**
     * @return An instance of {@link TestDriver}. This exposes additional functionality that is
     * useful in the context of testing when using WorkManager.
     * @deprecated Call {@link WorkManagerTestInitHelper#getTestDriver(Context)} instead.
     */
    @Deprecated
    public static @Nullable TestDriver getTestDriver() {
        WorkManagerImpl workManager = WorkManagerImpl.getInstance();
        if (workManager == null) {
            return null;
        } else {
            return TestWorkManagerImplKt.getTestDriver(workManager);
        }
    }

    /**
     * @return An instance of {@link TestDriver}. This exposes additional functionality that is
     * useful in the context of testing when using WorkManager.
     */
    public static @Nullable TestDriver getTestDriver(@NonNull Context context) {
        try {
            return TestWorkManagerImplKt.getTestDriver(WorkManagerImpl.getInstance(context));
        } catch (IllegalStateException e) {
            return null;
        }
    }

    /**
     * Closes internal {@link androidx.work.WorkManager}'s database.
     * <p>
     * It could be helpful to avoid warnings by CloseGuard in testing infra. You need to be
     * make sure that {@code WorkManager} finished all operations and won't touch database
     * anymore. Meaning that both {@link Configuration#getTaskExecutor()} and
     * {@link Configuration#getExecutor()} are idle.
     */
    @SuppressWarnings("deprecation")
    public static void closeWorkDatabase() {
        WorkManagerImpl workManager = WorkManagerImpl.getInstance();
        if (workManager != null) {
            WorkDatabase workDatabase = workManager.getWorkDatabase();
            workDatabase.close();
        }
    }

    private WorkManagerTestInitHelper() {
    }
}