Add tests for parallel processing of work.
Introduces infrastracture for delaying when we complete
work, to simulate multiple threads operating on work in
parallel, and use it for some new tests.
Test: bit CtsJobSchedulerTestCases:.EnqueueJobWorkTest
Change-Id: I19d6b46d69532731e579dc65ff8715605a3a313c
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 94aee20..964853c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -52,6 +52,8 @@
ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
+ ArrayList<JobWorkItem> mPendingCompletions = new ArrayList<>();
+
private boolean mWaitingForStop;
@Override
@@ -104,6 +106,8 @@
Log.i(TAG, "Received work #" + index + ": " + work.getIntent());
mReceivedWork.add(work);
+ int flags = 0;
+
if (index < expectedWork.length) {
TestWorkItem expected = expectedWork[index];
int grantFlags = work.getIntent().getFlags();
@@ -170,15 +174,33 @@
}
}
- if ((expected.flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
+ flags = expected.flags;
+
+ if ((flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
Log.i(TAG, "Now waiting to stop");
mWaitingForStop = true;
TestEnvironment.getTestEnvironment().notifyWaitingForStop();
return true;
}
+
+ if ((flags & TestWorkItem.FLAG_COMPLETE_NEXT) != 0) {
+ if (!processNextPendingCompletion()) {
+ TestEnvironment.getTestEnvironment().notifyExecution(params,
+ 0, 0, null,
+ "Expected to complete next pending work but there was none: "
+ + " @ #" + index);
+ return false;
+ }
+ }
}
- mParams.completeWork(work);
+ if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) != 0) {
+ mPendingCompletions.add(work);
+ } else if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) != 0) {
+ mPendingCompletions.add(0, work);
+ } else {
+ mParams.completeWork(work);
+ }
if (index < expectedWork.length) {
TestWorkItem expected = expectedWork[index];
@@ -195,6 +217,20 @@
index++;
}
+
+ if (processNextPendingCompletion()) {
+ // We had some pending completions, clean them all out...
+ while (processNextPendingCompletion()) {
+ }
+ // ...and we need to do a final dequeue to complete the job, which should not
+ // return any remaining work.
+ if ((work = params.dequeueWork()) != null) {
+ TestEnvironment.getTestEnvironment().notifyExecution(params,
+ 0, 0, null,
+ "Expected no remaining work after dequeue pending, but got: " + work);
+ }
+ }
+
Log.i(TAG, "Done with all work at #" + index);
// We don't notifyExecution here because we want to make sure the job properly
// stops itself.
@@ -219,6 +255,16 @@
}
}
+ boolean processNextPendingCompletion() {
+ if (mPendingCompletions.size() <= 0) {
+ return false;
+ }
+
+ JobWorkItem next = mPendingCompletions.remove(0);
+ mParams.completeWork(next);
+ return true;
+ }
+
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Received stop callback");
@@ -227,7 +273,24 @@
}
public static final class TestWorkItem {
+ /**
+ * Stop processing work for now, waiting for the service to be stopped.
+ */
public static final int FLAG_WAIT_FOR_STOP = 1<<0;
+ /**
+ * Don't complete this work now, instead push it on the back of the stack of
+ * pending completions.
+ */
+ public static final int FLAG_DELAY_COMPLETE_PUSH_BACK = 1<<1;
+ /**
+ * Don't complete this work now, instead insert to the top of the stack of
+ * pending completions.
+ */
+ public static final int FLAG_DELAY_COMPLETE_PUSH_TOP = 1<<2;
+ /**
+ * Complete next pending completion on the stack before completing this one.
+ */
+ public static final int FLAG_COMPLETE_NEXT = 1<<3;
public final Intent intent;
public final JobInfo jobInfo;
@@ -247,6 +310,16 @@
requireUrisNotGranted = null;
}
+ public TestWorkItem(Intent _intent, int _flags) {
+ intent = _intent;
+ jobInfo = null;
+ flags = _flags;
+ deliveryCount = 1;
+ subitems = null;
+ requireUrisGranted = null;
+ requireUrisNotGranted = null;
+ }
+
public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
intent = _intent;
jobInfo = null;
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
index 6d2599f..604ccce 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
@@ -186,6 +186,87 @@
}
/**
+ * Test basic enqueueing batches of work that will be executed in parallel.
+ */
+ public void testEnqueueParallel2Work() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
+ * Test basic enqueueing batches of work that will be executed in parallel and completed
+ * in reverse order.
+ */
+ public void testEnqueueParallel2ReverseWork() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
+ * Test basic enqueueing batches of work that will be executed in parallel.
+ */
+ public void testEnqueueMultipleParallelWork() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ Intent work3 = new Intent("work3");
+ Intent work4 = new Intent("work4");
+ Intent work5 = new Intent("work5");
+ Intent work6 = new Intent("work6");
+ Intent work7 = new Intent("work7");
+ Intent work8 = new Intent("work8");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK
+ | TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work4, TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work5, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work6, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work7, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP
+ | TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work8) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work7));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work8));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
* Test job getting stopped while processing work and that work being redelivered.
*/
public void testEnqueueMultipleRedeliver() throws Exception {
@@ -238,6 +319,61 @@
}
/**
+ * Test job getting stopped while processing work in parallel and that work being redelivered.
+ */
+ public void testEnqueueMultipleParallelRedeliver() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ Intent work3 = new Intent("work3");
+ Intent work4 = new Intent("work4");
+ Intent work5 = new Intent("work5");
+ Intent work6 = new Intent("work6");
+ TestWorkItem[] initialWork = new TestWorkItem[] {
+ new TestWorkItem(work1),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work4, TestWorkItem.FLAG_WAIT_FOR_STOP, 1) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWaitForStop();
+ kTestEnvironment.setExpectedWork(initialWork);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+ kTestEnvironment.readyToWork();
+
+ // Now wait for the job to get to the point where it is processing the last
+ // work and waiting for it to be stopped.
+ assertTrue("Job with work enqueued did not wait to stop.",
+ kTestEnvironment.awaitWaitingForStop());
+
+ // Cause the job to timeout (stop) immediately, and wait for its execution to finish.
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler timeout "
+ + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+ assertTrue("Job with work enqueued did not finish.",
+ kTestEnvironment.awaitExecution());
+ compareWork(initialWork, kTestEnvironment.getLastReceivedWork());
+
+ // Now we are going to add some more work, restart the job, and see if it correctly
+ // redelivers the last work and delivers the new work.
+ TestWorkItem[] finalWork = new TestWorkItem[] {
+ new TestWorkItem(work2, 0, 2), new TestWorkItem(work3, 0, 2),
+ new TestWorkItem(work4, 0, 2), new TestWorkItem(work5), new TestWorkItem(work6) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(finalWork);
+ mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+ kTestEnvironment.readyToWork();
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run "
+ + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+
+ assertTrue("Restarted with work enqueued did not execute.",
+ kTestEnvironment.awaitExecution());
+ compareWork(finalWork, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
* Test basic enqueueing batches of work.
*/
public void testEnqueueMultipleUriGrantWork() throws Exception {