blob: 20121dc72c22fcc595c323070aae5ff1a635bbfa [file] [log] [blame]
/*
* Copyright 2017 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.impl;
import static androidx.work.WorkInfo.State.BLOCKED;
import static androidx.work.WorkInfo.State.CANCELLED;
import static androidx.work.WorkInfo.State.ENQUEUED;
import static androidx.work.WorkInfo.State.FAILED;
import static androidx.work.WorkInfo.State.RUNNING;
import static androidx.work.WorkInfo.State.SUCCEEDED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.isOneOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.work.ArrayCreatingInputMerger;
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.DatabaseTest;
import androidx.work.ListenableWorker;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.ProgressUpdater;
import androidx.work.WorkerParameters;
import androidx.work.impl.model.Dependency;
import androidx.work.impl.model.DependencyDao;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.SynchronousExecutor;
import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
import androidx.work.worker.ChainedArgumentWorker;
import androidx.work.worker.EchoingWorker;
import androidx.work.worker.ExceptionWorker;
import androidx.work.worker.FailureWorker;
import androidx.work.worker.InfiniteTestWorker;
import androidx.work.worker.InterruptionAwareWorker;
import androidx.work.worker.LatchWorker;
import androidx.work.worker.RetryWorker;
import androidx.work.worker.ReturnNullResultWorker;
import androidx.work.worker.SleepTestWorker;
import androidx.work.worker.TestWorker;
import androidx.work.worker.UsedWorker;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class WorkerWrapperTest extends DatabaseTest {
private Configuration mConfiguration;
private TaskExecutor mWorkTaskExecutor;
private WorkSpecDao mWorkSpecDao;
private DependencyDao mDependencyDao;
private Context mContext;
private Scheduler mMockScheduler;
private ProgressUpdater mMockProgressUpdater;
private Executor mSynchronousExecutor = new SynchronousExecutor();
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mConfiguration = new Configuration.Builder()
.setExecutor(new SynchronousExecutor())
.setMinimumLoggingLevel(Log.VERBOSE)
.build();
mWorkTaskExecutor = new InstantWorkTaskExecutor();
mWorkSpecDao = mDatabase.workSpecDao();
mDependencyDao = mDatabase.dependencyDao();
mMockScheduler = mock(Scheduler.class);
mMockProgressUpdater = mock(ProgressUpdater.class);
}
@Test
@SmallTest
public void testSuccess() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(SUCCEEDED));
}
@Test
@SmallTest
public void testRunAttemptCountIncremented_successfulExecution() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
}
@Test
@SmallTest
public void testRunAttemptCountIncremented_failedExecution() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
insertWork(work);
createBuilder(work.getStringId())
.build()
.run();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
}
@Test
@SmallTest
public void testPermanentErrorWithInvalidWorkSpecId() {
final String invalidWorkSpecId = "INVALID_ID";
WorkerWrapper workerWrapper = createBuilder(invalidWorkSpecId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
}
@Test
@SmallTest
public void testInvalidWorkerClassName() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
work.getWorkSpec().workerClassName = "dummy";
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
verify(mMockScheduler, never()).schedule(any(WorkSpec[].class));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@SmallTest
public void testUsedWorker_failsExecution() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
UsedWorker usedWorker = (UsedWorker) mConfiguration.getWorkerFactory()
.createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
UsedWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withWorker(usedWorker)
.build();
workerWrapper.run();
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@SmallTest
public void testNotEnqueued() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(RUNNING)
.build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(true));
}
@Test
@SmallTest
public void testCancelled() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(CANCELLED)
.build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(CANCELLED));
}
@Test
@SmallTest
public void testPermanentErrorWithInvalidWorkerClass() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
getWorkSpec(work).workerClassName = "INVALID_CLASS_NAME";
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@SmallTest
public void testPermanentErrorWithInvalidInputMergerClass() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
getWorkSpec(work).inputMergerClassName = "INVALID_CLASS_NAME";
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@SmallTest
public void testFailed() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@LargeTest
public void testFailedOnDeepHierarchy() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
insertWork(work);
String previousId = work.getStringId();
String firstWorkId = previousId;
for (int i = 0; i < 500; ++i) {
work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
insertWork(work);
mDependencyDao.insertDependency(new Dependency(work.getStringId(), previousId));
previousId = work.getStringId();
}
WorkerWrapper workerWrapper = createBuilder(firstWorkId).build();
workerWrapper.setFailedAndResolve();
assertThat(mWorkSpecDao.getState(firstWorkId), is(FAILED));
assertThat(mWorkSpecDao.getState(previousId), is(FAILED));
}
@Test
@LargeTest
public void testRunning() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
insertWork(work);
WorkerWrapper wrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(wrapper);
Executors.newSingleThreadExecutor().submit(wrapper);
Thread.sleep(2000L); // Async wait duration.
assertThat(mWorkSpecDao.getState(work.getStringId()), is(RUNNING));
Thread.sleep(SleepTestWorker.SLEEP_DURATION);
assertThat(listener.mResult, is(false));
}
@Test
@SmallTest
public void testRunning_onlyWhenEnqueued() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(RUNNING)
.build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(true));
}
@Test
@SmallTest
public void testDependencies() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED).build();
Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
mDependencyDao.insertDependency(dependency);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(ENQUEUED));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(BLOCKED));
assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getStringId()), is(false));
createBuilder(prerequisiteWork.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(SUCCEEDED));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED));
assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getStringId()), is(true));
ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
verify(mMockScheduler).schedule(captor.capture());
assertThat(captor.getValue().id, is(work.getStringId()));
}
@Test
@SmallTest
public void testDependencies_passesOutputs() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(ChainedArgumentWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED)
.build();
Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
mDependencyDao.insertDependency(dependency);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
createBuilder(prerequisiteWork.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build().run();
List<Data> arguments = mWorkSpecDao.getInputsFromPrerequisites(work.getStringId());
assertThat(arguments.size(), is(1));
assertThat(arguments, contains(ChainedArgumentWorker.getChainedArguments()));
}
@Test
@SmallTest
public void testDependencies_passesMergedOutputs() {
String key = "key";
String value1 = "value1";
String value2 = "value2";
OneTimeWorkRequest prerequisiteWork1 = new OneTimeWorkRequest.Builder(EchoingWorker.class)
.setInputData(new Data.Builder().putString(key, value1).build())
.build();
OneTimeWorkRequest prerequisiteWork2 = new OneTimeWorkRequest.Builder(EchoingWorker.class)
.setInputData(new Data.Builder().putString(key, value2).build())
.build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInputMerger(ArrayCreatingInputMerger.class)
.build();
Dependency dependency1 =
new Dependency(work.getStringId(), prerequisiteWork1.getStringId());
Dependency dependency2 =
new Dependency(work.getStringId(), prerequisiteWork2.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork1);
insertWork(prerequisiteWork2);
insertWork(work);
mDependencyDao.insertDependency(dependency1);
mDependencyDao.insertDependency(dependency2);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
// Run the prerequisites.
createBuilder(prerequisiteWork1.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build().run();
createBuilder(prerequisiteWork2.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build().run();
// Create and run the dependent work.
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
workerWrapper.run();
Data input = workerWrapper.mWorker.getInputData();
assertThat(input.size(), is(1));
assertThat(Arrays.asList(input.getStringArray(key)),
containsInAnyOrder(value1, value2));
}
@Test
@SmallTest
public void testDependencies_setsPeriodStartTimesForUnblockedWork() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED)
.build();
Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
mDependencyDao.insertDependency(dependency);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
long beforeUnblockedTime = System.currentTimeMillis();
createBuilder(prerequisiteWork.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(workSpec.periodStartTime, is(greaterThan(beforeUnblockedTime)));
}
@Test
@SmallTest
public void testDependencies_enqueuesBlockedDependentsOnSuccess() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED)
.build();
OneTimeWorkRequest cancelledWork = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(CANCELLED)
.build();
Dependency dependency1 = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
Dependency dependency2 =
new Dependency(cancelledWork.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
insertWork(cancelledWork);
mDependencyDao.insertDependency(dependency1);
mDependencyDao.insertDependency(dependency2);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
createBuilder(prerequisiteWork.getStringId())
.build()
.run();
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(SUCCEEDED));
assertThat(mWorkSpecDao.getState(work.getStringId()),
isOneOf(ENQUEUED, RUNNING, SUCCEEDED));
assertThat(mWorkSpecDao.getState(cancelledWork.getStringId()), is(CANCELLED));
}
@Test
@SmallTest
public void testDependencies_failsUncancelledDependentsOnFailure() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(FailureWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED)
.build();
OneTimeWorkRequest cancelledWork = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(CANCELLED)
.build();
Dependency dependency1 = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
Dependency dependency2 =
new Dependency(cancelledWork.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
insertWork(cancelledWork);
mDependencyDao.insertDependency(dependency1);
mDependencyDao.insertDependency(dependency2);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
createBuilder(prerequisiteWork.getStringId())
.build()
.run();
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(FAILED));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
assertThat(mWorkSpecDao.getState(cancelledWork.getStringId()), is(CANCELLED));
}
@Test
@SmallTest
public void testBackedOffOneTimeWork_doesNotRun() {
OneTimeWorkRequest retryWork =
new OneTimeWorkRequest.Builder(RetryWorker.class).build();
long future = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1);
mDatabase.beginTransaction();
try {
mWorkSpecDao.insertWorkSpec(retryWork.getWorkSpec());
mWorkSpecDao.setPeriodStartTime(retryWork.getStringId(), future);
mWorkSpecDao.incrementWorkSpecRunAttemptCount(retryWork.getStringId());
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
createBuilder(retryWork.getStringId())
.build()
.run();
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(retryWork.getStringId());
// The run attempt count should remain the same
assertThat(workSpec.runAttemptCount, is(1));
}
@Test
@SmallTest
public void testRun_periodicWork_success_updatesPeriodStartTime() {
long intervalDuration = PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS;
long periodStartTime = System.currentTimeMillis();
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
TestWorker.class, intervalDuration, TimeUnit.MILLISECONDS).build();
getWorkSpec(periodicWork).periodStartTime = periodStartTime;
insertWork(periodicWork);
createBuilder(periodicWork.getStringId())
.build()
.run();
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTime));
}
@Test
@SmallTest
public void testRun_periodicWork_failure_updatesPeriodStartTime() {
long intervalDuration = PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS;
long periodStartTime = System.currentTimeMillis();
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
FailureWorker.class, intervalDuration, TimeUnit.MILLISECONDS).build();
getWorkSpec(periodicWork).periodStartTime = periodStartTime;
insertWork(periodicWork);
createBuilder(periodicWork.getStringId())
.build()
.run();
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTime));
}
@Test
@SmallTest
public void testPeriodicWork_success() {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
TestWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS)
.build();
final String periodicWorkId = periodicWork.getStringId();
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId)
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(false));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
// SystemAlarmScheduler needs to reschedule the same worker.
if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL) {
ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
verify(mMockScheduler, atLeast(1))
.schedule(captor.capture());
WorkSpec workSpec = captor.getValue();
assertThat(workSpec.id, is(periodicWorkId));
}
}
@Test
@SmallTest
public void testPeriodicWork_fail() {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
FailureWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS)
.build();
final String periodicWorkId = periodicWork.getStringId();
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(false));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
}
@Test
@SmallTest
public void testPeriodicWork_retry() {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
RetryWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS)
.build();
final String periodicWorkId = periodicWork.getStringId();
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(true));
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(1));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
}
@Test
@SmallTest
public void testPeriodic_dedupe() {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
TestWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS)
.build();
final String periodicWorkId = periodicWork.getStringId();
final WorkSpec workSpec = periodicWork.getWorkSpec();
long now = System.currentTimeMillis();
workSpec.periodStartTime = now + workSpec.intervalDuration;
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
// Should get rescheduled
assertThat(listener.mResult, is(true));
}
@Test
@SmallTest
public void testPeriodic_firstRun_flexApplied_noDedupe() {
PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(
TestWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
TimeUnit.MILLISECONDS,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
TimeUnit.MILLISECONDS)
.build();
final String periodicWorkId = periodicWork.getStringId();
final WorkSpec workSpec = periodicWork.getWorkSpec();
workSpec.periodStartTime = 0;
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
// Should not get rescheduled
assertThat(listener.mResult, is(false));
}
@Test
@SmallTest
public void testScheduler() {
OneTimeWorkRequest prerequisiteWork =
new OneTimeWorkRequest.Builder(TestWorker.class).build();
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(BLOCKED).build();
Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
mDatabase.beginTransaction();
try {
insertWork(prerequisiteWork);
insertWork(work);
mDependencyDao.insertDependency(dependency);
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
createBuilder(prerequisiteWork.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
verify(mMockScheduler).schedule(captor.capture());
assertThat(captor.getValue().id, is(work.getStringId()));
}
@Test
@SmallTest
public void testFromWorkSpec_hasAppContext() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.getApplicationContext(), is(equalTo(mContext.getApplicationContext())));
}
@Test
@SmallTest
public void testFromWorkSpec_hasCorrectArguments() {
String key = "KEY";
String expectedValue = "VALUE";
Data input = new Data.Builder().putString(key, expectedValue).build();
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(TestWorker.class).setInputData(input).build();
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
new WorkerParameters(
work.getId(),
input,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.getInputData().getString(key), is(expectedValue));
work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.getInputData().size(), is(0));
}
@Test
@SmallTest
public void testFromWorkSpec_hasCorrectTags() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(TestWorker.class)
.addTag("one")
.addTag("two")
.addTag("three")
.build();
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
Arrays.asList("one", "two", "three"),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.getTags(), containsInAnyOrder("one", "two", "three"));
}
@Test
@SmallTest
public void testFromWorkSpec_hasCorrectRuntimeExtras() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(TestWorker.class).build();
WorkerParameters.RuntimeExtras runtimeExtras = new WorkerParameters.RuntimeExtras();
runtimeExtras.triggeredContentAuthorities = Arrays.asList("tca1", "tca2", "tca3");
runtimeExtras.triggeredContentUris = Arrays.asList(Uri.parse("tcu1"), Uri.parse("tcu2"));
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
runtimeExtras,
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.getTriggeredContentAuthorities(),
containsInAnyOrder(runtimeExtras.triggeredContentAuthorities.toArray()));
assertThat(worker.getTriggeredContentUris(),
containsInAnyOrder(runtimeExtras.triggeredContentUris.toArray()));
}
@Test
@SmallTest
public void testSuccess_withPendingScheduledWork() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(unscheduled);
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
verify(mMockScheduler, times(1)).schedule(unscheduled.getWorkSpec());
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(SUCCEEDED));
}
@Test
@SmallTest
public void testFailure_withPendingScheduledWork() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
insertWork(work);
OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(unscheduled);
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
verify(mMockScheduler, times(1)).schedule(unscheduled.getWorkSpec());
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@LargeTest
public void testInterruption() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
Executors.newSingleThreadExecutor().submit(workerWrapper);
workerWrapper.interrupt(false);
Thread.sleep(6000L);
assertThat(listener.mResult, is(true));
}
@Test
@LargeTest
public void testPruneWhileRunning_callsSchedulerCancel() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(LatchWorker.class).build();
insertWork(work);
LatchWorker latchWorker =
(LatchWorker) mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
LatchWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
Executors.newSingleThreadExecutor(),
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.withWorker(latchWorker)
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
Executors.newSingleThreadExecutor().submit(workerWrapper);
Thread.sleep(1000L);
mDatabase.workSpecDao().delete(work.getStringId());
assertThat(latchWorker.mLatch.getCount(), is(greaterThan(0L)));
latchWorker.mLatch.countDown();
Thread.sleep(1000L);
assertThat(listener.mResult, is(notNullValue()));
verify(mMockScheduler, times(1)).cancel(work.getStringId());
}
@Test
@SmallTest
public void testInterruptionWithoutCancellation_isMarkedOnRunningWorker() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(InterruptionAwareWorker.class).build();
insertWork(work);
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
InterruptionAwareWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.isStopped(), is(false));
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.withWorker(worker)
.build();
Executors.newSingleThreadExecutor().submit(workerWrapper);
workerWrapper.interrupt(false);
assertThat(worker.isStopped(), is(true));
}
@Test
@SmallTest
public void testInterruptionWithCancellation_isMarkedOnRunningWorker() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(InterruptionAwareWorker.class).build();
insertWork(work);
ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
InterruptionAwareWorker.class.getName(),
new WorkerParameters(
work.getId(),
Data.EMPTY,
Collections.<String>emptyList(),
new WorkerParameters.RuntimeExtras(),
1,
mSynchronousExecutor,
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
mMockProgressUpdater));
assertThat(worker, is(notNullValue()));
assertThat(worker.isStopped(), is(false));
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.withWorker(worker)
.build();
Executors.newSingleThreadExecutor().submit(workerWrapper);
workerWrapper.interrupt(true);
assertThat(worker.isStopped(), is(true));
}
@Test
@SmallTest
public void testException_isTreatedAsFailure() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class).build();
insertWork(work);
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@LargeTest
public void testWorkerWrapper_handlesWorkSpecDeletion() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
Executors.newSingleThreadExecutor().submit(workerWrapper);
mWorkSpecDao.delete(work.getStringId());
Thread.sleep(6000L);
assertThat(listener.mResult, is(false));
}
@Test
@LargeTest
public void testWorker_getsRunAttemptCount() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class)
.setInitialRunAttemptCount(10)
.build();
insertWork(work);
WorkerWrapper workerWrapper =
createBuilder(work.getStringId())
.withSchedulers(Collections.singletonList(mMockScheduler))
.build();
Executors.newSingleThreadExecutor().submit(workerWrapper);
Thread.sleep(1000L);
assertThat(workerWrapper.mWorker.getRunAttemptCount(), is(10));
}
@Test
@SmallTest
public void testWorkerThatReturnsNullResult() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(ReturnNullResultWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
@SmallTest
public void testWorkerThatThrowsAnException() {
OneTimeWorkRequest work =
new OneTimeWorkRequest.Builder(ExceptionWorker.class).build();
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
private WorkerWrapper.Builder createBuilder(String workSpecId) {
return new WorkerWrapper.Builder(
mContext,
mConfiguration,
mWorkTaskExecutor,
mDatabase,
workSpecId);
}
private FutureListener createAndAddFutureListener(WorkerWrapper workerWrapper) {
ListenableFuture<Boolean> future = workerWrapper.getFuture();
FutureListener listener = new FutureListener(future);
future.addListener(listener, mSynchronousExecutor);
return listener;
}
private static class FutureListener implements Runnable {
ListenableFuture<Boolean> mFuture;
Boolean mResult;
FutureListener(ListenableFuture<Boolean> future) {
mFuture = future;
}
@Override
public void run() {
try {
mResult = mFuture.get();
} catch (InterruptedException | ExecutionException e) {
// Do nothing.
}
}
}
}