Keep track of completed work in TestScheduler.
* This is a workaround for incorrect behavior for tests where we did
correctly switch to a synchronous internal task executor thereby
requiring that users call setInitialDelayMet() or setAllConstraintsMet().
* Now the TestScheduler tracks worker completion and does not throw
an exception for WorkSpecs that it knows about.
Test: Added unit tests.
Fixes: b/139190782
Change-Id: I42b53338650c774ba26fdbeffa5c037067786cf2
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
index 3bbb0bb..868ce63 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
@@ -161,6 +161,31 @@
}
}
+ @Test
+ public void testWorker_afterSuccessfulRun_postConditions()
+ throws InterruptedException, ExecutionException {
+
+ OneTimeWorkRequest request = createWorkRequest();
+ WorkManager workManager = WorkManager.getInstance(mContext);
+ workManager.enqueue(request).getResult().get();
+ WorkInfo status = workManager.getWorkInfoById(request.getId()).get();
+ assertThat(status.getState().isFinished(), is(true));
+ mTestDriver.setAllConstraintsMet(request.getId());
+ mTestDriver.setInitialDelayMet(request.getId());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWorker_afterSuccessfulRun_throwsExceptionWhenSetPeriodDelayMet()
+ throws InterruptedException, ExecutionException {
+
+ OneTimeWorkRequest request = createWorkRequest();
+ WorkManager workManager = WorkManager.getInstance(mContext);
+ workManager.enqueue(request).getResult().get();
+ WorkInfo status = workManager.getWorkInfoById(request.getId()).get();
+ assertThat(status.getState().isFinished(), is(true));
+ mTestDriver.setPeriodDelayMet(request.getId());
+ }
+
private static OneTimeWorkRequest createWorkRequest() {
return new OneTimeWorkRequest.Builder(TestWorker.class).build();
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
index 4a526ce..afc4f41 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
@@ -47,13 +47,15 @@
class TestScheduler implements Scheduler, ExecutionListener {
private final Context mContext;
- private final Map<String, InternalWorkState> mInternalWorkStates;
+ private final Map<String, InternalWorkState> mPendingWorkStates;
+ private final Map<String, InternalWorkState> mTerminatedWorkStates;
private static final Object sLock = new Object();
TestScheduler(@NonNull Context context) {
mContext = context;
- mInternalWorkStates = new HashMap<>();
+ mPendingWorkStates = new HashMap<>();
+ mTerminatedWorkStates = new HashMap<>();
}
@Override
@@ -65,8 +67,8 @@
synchronized (sLock) {
List<String> workSpecIdsToSchedule = new ArrayList<>(workSpecs.length);
for (WorkSpec workSpec : workSpecs) {
- if (!mInternalWorkStates.containsKey(workSpec.id)) {
- mInternalWorkStates.put(workSpec.id, new InternalWorkState(mContext, workSpec));
+ if (!mPendingWorkStates.containsKey(workSpec.id)) {
+ mPendingWorkStates.put(workSpec.id, new InternalWorkState(mContext, workSpec));
}
workSpecIdsToSchedule.add(workSpec.id);
}
@@ -78,7 +80,11 @@
public void cancel(@NonNull String workSpecId) {
synchronized (sLock) {
WorkManagerImpl.getInstance(mContext).stopWork(workSpecId);
- mInternalWorkStates.remove(workSpecId);
+ mPendingWorkStates.remove(workSpecId);
+ // We don't need to keep track of cancelled workSpecs. This is because subsequent calls
+ // to enqueue() will no-op because insertWorkSpec in WorkDatabase has a conflict
+ // policy of @Ignore. So TestScheduler will _never_ be asked to schedule those
+ // WorkSpecs.
}
}
@@ -91,13 +97,16 @@
*/
void setAllConstraintsMet(@NonNull UUID workSpecId) {
synchronized (sLock) {
- InternalWorkState internalWorkState = mInternalWorkStates.get(workSpecId.toString());
- if (internalWorkState == null) {
- throw new IllegalArgumentException(
- "Work with id " + workSpecId + " is not enqueued!");
+ String id = workSpecId.toString();
+ if (!mTerminatedWorkStates.containsKey(id)) {
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
+ if (internalWorkState == null) {
+ throw new IllegalArgumentException(
+ "Work with id " + workSpecId + " is not enqueued!");
+ }
+ internalWorkState.mConstraintsMet = true;
+ scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
- internalWorkState.mConstraintsMet = true;
- scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
}
@@ -110,13 +119,16 @@
*/
void setInitialDelayMet(@NonNull UUID workSpecId) {
synchronized (sLock) {
- InternalWorkState internalWorkState = mInternalWorkStates.get(workSpecId.toString());
- if (internalWorkState == null) {
- throw new IllegalArgumentException(
- "Work with id " + workSpecId + " is not enqueued!");
+ String id = workSpecId.toString();
+ if (!mTerminatedWorkStates.containsKey(id)) {
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
+ if (internalWorkState == null) {
+ throw new IllegalArgumentException(
+ "Work with id " + workSpecId + " is not enqueued!");
+ }
+ internalWorkState.mInitialDelayMet = true;
+ scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
- internalWorkState.mInitialDelayMet = true;
- scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
}
@@ -129,7 +141,8 @@
*/
void setPeriodDelayMet(@NonNull UUID workSpecId) {
synchronized (sLock) {
- InternalWorkState internalWorkState = mInternalWorkStates.get(workSpecId.toString());
+ String id = workSpecId.toString();
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
if (internalWorkState == null) {
throw new IllegalArgumentException(
"Work with id " + workSpecId + " is not enqueued!");
@@ -141,20 +154,22 @@
@Override
public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
-
synchronized (sLock) {
- InternalWorkState internalWorkState = mInternalWorkStates.get(workSpecId);
- if (internalWorkState.mWorkSpec.isPeriodic()) {
- internalWorkState.reset();
- } else {
- mInternalWorkStates.remove(workSpecId);
+ InternalWorkState internalWorkState = mPendingWorkStates.get(workSpecId);
+ if (internalWorkState != null) {
+ if (internalWorkState.mWorkSpec.isPeriodic()) {
+ internalWorkState.reset();
+ } else {
+ mTerminatedWorkStates.put(workSpecId, internalWorkState);
+ mPendingWorkStates.remove(workSpecId);
+ }
}
}
}
private void scheduleInternal(Collection<String> workSpecIds) {
for (String workSpecId : workSpecIds) {
- InternalWorkState internalWorkState = mInternalWorkStates.get(workSpecId);
+ InternalWorkState internalWorkState = mPendingWorkStates.get(workSpecId);
if (internalWorkState.isRunnable()) {
WorkManagerImpl.getInstance(mContext).startWork(workSpecId);
}