blob: f4a3a7b09bee2c606cbf3b0819249ff11bdfecdc [file] [log] [blame]
/*
* 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.impl;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.State;
import androidx.work.TestLifecycleOwner;
import androidx.work.WorkContinuation;
import androidx.work.WorkManagerTest;
import androidx.work.WorkStatus;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
import androidx.work.worker.TestWorker;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class WorkContinuationImplTest extends WorkManagerTest {
private Configuration mConfiguration;
private WorkDatabase mDatabase;
private WorkManagerImpl mWorkManagerImpl;
private Scheduler mScheduler;
@Rule
public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
@Before
public void setUp() {
ArchTaskExecutor.getInstance().setDelegate(new android.arch.core.executor.TaskExecutor() {
@Override
public void executeOnDiskIO(@NonNull Runnable runnable) {
runnable.run();
}
@Override
public void postToMainThread(@NonNull Runnable runnable) {
runnable.run();
}
@Override
public boolean isMainThread() {
return true;
}
});
TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.mLifecycleRegistry.markState(Lifecycle.State.CREATED);
mScheduler = mock(Scheduler.class);
Context context = InstrumentationRegistry.getTargetContext();
mConfiguration = new Configuration.Builder()
.setExecutor(Executors.newSingleThreadExecutor())
.build();
mWorkManagerImpl = spy(new WorkManagerImpl(context, mConfiguration));
when(mWorkManagerImpl.getSchedulers()).thenReturn(Collections.singletonList(mScheduler));
WorkManagerImpl.setDelegate(mWorkManagerImpl);
mDatabase = mWorkManagerImpl.getWorkDatabase();
}
@After
public void tearDown() {
List<String> ids = mDatabase.workSpecDao().getAllWorkSpecIds();
for (String id : ids) {
mWorkManagerImpl.cancelWorkByIdSync(UUID.fromString(id));
}
WorkManagerImpl.setDelegate(null);
ArchTaskExecutor.getInstance().setDelegate(null);
}
@Test
public void testContinuation_noParent() {
OneTimeWorkRequest testWork = createTestWorker();
WorkContinuationImpl continuation =
new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(testWork));
assertThat(continuation.getParents(), is(nullValue()));
assertThat(continuation.getIds().size(), is(1));
assertThat(continuation.getIds().get(0), is(testWork.getStringId()));
assertThat(continuation.getAllIds().size(), is(1));
}
@Test
public void testContinuation_singleChain() {
OneTimeWorkRequest testWork = createTestWorker();
OneTimeWorkRequest dependentWork = createTestWorker();
WorkContinuationImpl continuation =
new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(testWork));
WorkContinuationImpl dependent = (WorkContinuationImpl) (continuation.then(
dependentWork));
assertThat(dependent.getParents(), containsInAnyOrder(continuation));
assertThat(dependent.getIds().size(), is(1));
assertThat(dependent.getIds().get(0), is(dependentWork.getStringId()));
assertThat(dependent.getAllIds().size(), is(2));
assertThat(
dependent.getAllIds(),
containsInAnyOrder(dependentWork.getStringId(), testWork.getStringId()));
}
@Test
public void testContinuation_enqueue() {
WorkContinuationImpl continuation = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
assertThat(continuation.isEnqueued(), is(false));
continuation.enqueueSync();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
}
@Test
public void testContinuation_chainEnqueue() {
WorkContinuationImpl continuation =
new WorkContinuationImpl(mWorkManagerImpl, createTestWorkerList());
WorkContinuationImpl chain = (WorkContinuationImpl) (
continuation.then(createTestWorker()).then(createTestWorker(), createTestWorker()));
chain.enqueueSync();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
}
@Test
public void testContinuation_chainEnqueueNoOpOnRetry() {
WorkContinuationImpl continuation =
new WorkContinuationImpl(mWorkManagerImpl, createTestWorkerList());
WorkContinuationImpl chain = (WorkContinuationImpl) (
continuation.then(createTestWorker()).then(createTestWorker(), createTestWorker()));
chain.enqueueSync();
verifyEnqueued(continuation);
verifyScheduled(mScheduler, continuation);
WorkContinuationImpl spy = spy(chain);
spy.enqueueSync();
// Verify no more calls to markEnqueued().
verify(spy, times(0)).markEnqueued();
}
@Test
public void testContinuation_join() {
WorkContinuationImpl first = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl second = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(first,
second);
assertThat(dependent.getParents(), is(notNullValue()));
assertThat(dependent.getParents(), containsInAnyOrder(first, second));
}
public void testContinuation_withWorkJoin() {
WorkContinuationImpl first = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl second = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
OneTimeWorkRequest work = createTestWorker();
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(
work, first, second);
assertThat(dependent.getIds(), containsInAnyOrder(work.getStringId()));
assertThat(dependent.getParents(), is(notNullValue()));
assertThat(dependent.getParents(), containsInAnyOrder(first, second));
}
@Test
public void testContinuation_joinAndEnqueue() {
WorkContinuationImpl first = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl second = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl third = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl fourth = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl firstDependent = (WorkContinuationImpl) WorkContinuation.combine(
first, second);
WorkContinuationImpl secondDependent = (WorkContinuationImpl) WorkContinuation.combine(
third, fourth);
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(
firstDependent, secondDependent);
dependent.enqueueSync();
verifyEnqueued(dependent);
verifyScheduled(mScheduler, dependent);
}
@Test
public void testContinuation_joinAndEnqueueWithOverlaps() {
WorkContinuationImpl first = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl second = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl third = new WorkContinuationImpl(mWorkManagerImpl,
createTestWorkerList());
WorkContinuationImpl firstDependent = (WorkContinuationImpl) WorkContinuation.combine(
first, second);
WorkContinuationImpl secondDependent = (WorkContinuationImpl) WorkContinuation.combine(
first, third);
WorkContinuationImpl dependent = (WorkContinuationImpl) WorkContinuation.combine(
firstDependent, secondDependent);
dependent.enqueueSync();
verifyEnqueued(dependent);
verifyScheduled(mScheduler, dependent);
}
@Test
@LargeTest
@SuppressWarnings("unchecked")
public void testContinuation_joinPassesAllOutput() throws InterruptedException {
final String intTag = "myint";
final String stringTag = "mystring";
OneTimeWorkRequest firstWork = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(State.SUCCEEDED)
.build();
OneTimeWorkRequest secondWork = new OneTimeWorkRequest.Builder(TestWorker.class)
.setInitialState(State.SUCCEEDED)
.build();
WorkSpecDao workSpecDao = mDatabase.workSpecDao();
workSpecDao.insertWorkSpec(getWorkSpec(firstWork));
workSpecDao.insertWorkSpec(getWorkSpec(secondWork));
workSpecDao.setOutput(
firstWork.getStringId(),
new Data.Builder().putInt(intTag, 0).build());
workSpecDao.setOutput(
secondWork.getStringId(),
new Data.Builder().putInt(intTag, 1).putString(stringTag, "hello").build());
WorkContinuationImpl firstContinuation =
new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(firstWork));
WorkContinuationImpl secondContinuation =
new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(secondWork));
WorkContinuationImpl dependentContinuation =
(WorkContinuationImpl) WorkContinuation.combine(
firstContinuation, secondContinuation);
dependentContinuation.enqueueSync();
String joinId = null;
for (String id : dependentContinuation.getAllIds()) {
if (!firstWork.getStringId().equals(id) && !secondWork.getStringId().equals(id)) {
joinId = id;
break;
}
}
Thread.sleep(5000L);
// TODO(sumir): I can't seem to get this kicked off automatically, so I'm running it myself.
// Figure out what's going on here.
Context context = InstrumentationRegistry.getTargetContext();
new WorkerWrapper.Builder(context, mConfiguration, mDatabase, joinId)
.build()
.run();
assertThat(joinId, is(not(nullValue())));
WorkSpec joinWorkSpec = mDatabase.workSpecDao().getWorkSpec(joinId);
assertThat(joinWorkSpec, is(not(nullValue())));
assertThat(joinWorkSpec.state, is(State.SUCCEEDED));
Data output = joinWorkSpec.output;
int[] intArray = output.getIntArray(intTag);
assertThat(intArray, is(not(nullValue())));
Arrays.sort(intArray);
assertThat(Arrays.binarySearch(intArray, 0), is(not(-1)));
assertThat(Arrays.binarySearch(intArray, 1), is(not(-1)));
assertThat(output.getStringArray(stringTag), is(not(nullValue())));
assertThat(Arrays.asList(output.getStringArray(stringTag)), contains("hello"));
}
@Test
@SmallTest
public void testContinuation_hasCycles() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
WorkContinuation continuationB = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(bWork));
// B -> C
WorkContinuation continuationBC = continuationB.then(cWork);
// combine -> A, C
WorkContinuation join = WorkContinuation.combine(continuationA, continuationBC);
// withCycles -> B
WorkContinuationImpl withCycles = (WorkContinuationImpl) join.then(bWork);
assertThat(withCycles.hasCycles(), is(true));
}
@Test
@SmallTest
public void testContinuation_hasCycles2() {
OneTimeWorkRequest aWork = createTestWorker(); // A
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
// A -> A
WorkContinuationImpl withCycles = (WorkContinuationImpl) continuationA.then(aWork);
assertThat(withCycles.hasCycles(), is(true));
}
@Test
@SmallTest
public void testContinuation_hasCycles3() {
OneTimeWorkRequest aWork = createTestWorker(); // A
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
// A -> A
WorkContinuation first = continuationA.then(aWork);
// A -> A
WorkContinuation second = continuationA.then(aWork);
// A
// A A
// A
WorkContinuationImpl joined = (WorkContinuationImpl) WorkContinuation.combine(
first, second);
assertThat(joined.hasCycles(), is(true));
}
@Test
@SmallTest
public void testContinuation_hasCycles4() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest cWork = createTestWorker(); // C
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
// A A
// B
WorkContinuation continuationB = WorkContinuation.combine(continuationA, continuationA);
// A A
// B
// C
WorkContinuation continuationC = continuationB.then(cWork);
// A A
// B
// C
// A
WorkContinuationImpl withCycles = (WorkContinuationImpl) continuationC.then(aWork);
assertThat(withCycles.hasCycles(), is(true));
}
@Test
@SmallTest
public void testContinuation_hasNoCycles() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
WorkContinuation continuationAB = new WorkContinuationImpl(
mWorkManagerImpl, Arrays.asList(aWork, bWork));
WorkContinuation continuationBC = new WorkContinuationImpl(
mWorkManagerImpl, Arrays.asList(bWork, cWork));
WorkContinuationImpl joined =
(WorkContinuationImpl) WorkContinuation.combine(continuationAB, continuationBC);
assertThat(joined.hasCycles(), is(false));
}
@Test
@SmallTest
public void testContinuation_hasNoCycles2() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
// A -> B
WorkContinuation continuationB = continuationA.then(bWork);
// A -> C
WorkContinuation continuationC = continuationA.then(cWork);
WorkContinuation continuationA2 = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
// A -> B
WorkContinuation continuationB2 = continuationA2.then(bWork);
// A -> C
WorkContinuation continuationC2 = continuationA2.then(cWork);
// A A
// B C B C
// D
WorkContinuationImpl joined = (WorkContinuationImpl) WorkContinuation.combine(
continuationB, continuationC, continuationB2, continuationC2);
assertThat(joined.hasCycles(), is(false));
}
@Test
@SmallTest
public void testContinuation_hasNoCycles3() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
WorkContinuation continuationA = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(aWork));
WorkContinuation continuationB = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(bWork));
WorkContinuation continuationC = new WorkContinuationImpl(
mWorkManagerImpl, Collections.singletonList(cWork));
WorkContinuation first = WorkContinuation.combine(continuationA, continuationB);
WorkContinuation second = WorkContinuation.combine(continuationA, continuationC);
WorkContinuationImpl joined = (WorkContinuationImpl) WorkContinuation.combine(
first, second);
assertThat(joined.hasCycles(), is(false));
}
@Test
@SmallTest
public void testGetStatusesSync() {
OneTimeWorkRequest aWork = createTestWorker(); // A
OneTimeWorkRequest bWork = createTestWorker(); // B
OneTimeWorkRequest cWork = createTestWorker(); // C
OneTimeWorkRequest dWork = createTestWorker(); // D
WorkContinuation firstChain = mWorkManagerImpl.beginWith(aWork).then(bWork);
WorkContinuation secondChain = mWorkManagerImpl.beginWith(cWork);
WorkContinuation combined = WorkContinuation.combine(dWork, firstChain, secondChain);
combined.synchronous().enqueueSync();
List<WorkStatus> statuses = combined.synchronous().getStatusesSync();
assertThat(statuses, is(notNullValue()));
List<UUID> ids = new ArrayList<>(statuses.size());
for (WorkStatus status : statuses) {
ids.add(status.getId());
}
assertThat(ids, containsInAnyOrder(
aWork.getId(), bWork.getId(), cWork.getId(), dWork.getId()));
}
private static void verifyEnqueued(WorkContinuationImpl continuation) {
assertThat(continuation.isEnqueued(), is(true));
List<WorkContinuationImpl> parents = continuation.getParents();
if (parents != null) {
for (WorkContinuationImpl parent : parents) {
verifyEnqueued(parent);
}
}
}
private static void verifyScheduled(Scheduler scheduler, WorkContinuationImpl continuation) {
Configuration configuration = continuation.getWorkManagerImpl().getConfiguration();
ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
verify(scheduler, times(1)).schedule(captor.capture());
List<WorkSpec> workSpecs = captor.getAllValues();
assertThat(workSpecs, notNullValue());
WorkDatabase workDatabase = continuation.getWorkManagerImpl().getWorkDatabase();
List<WorkSpec> eligibleWorkSpecs =
workDatabase
.workSpecDao()
.getEligibleWorkForScheduling(
configuration.getMaxSchedulerLimit());
Set<String> capturedIds = new HashSet<>();
for (WorkSpec workSpec : workSpecs) {
capturedIds.add(workSpec.id);
}
for (WorkSpec eligibleWorkSpec : eligibleWorkSpecs) {
assertThat(capturedIds.contains(eligibleWorkSpec.id), is(true));
}
}
private static OneTimeWorkRequest createTestWorker() {
return new OneTimeWorkRequest.Builder(TestWorker.class).build();
}
private static List<OneTimeWorkRequest> createTestWorkerList() {
return Collections.singletonList(createTestWorker());
}
}