JobStoreTest.java revision 1f0ec16b6d5f20ed8b6156eb1b5abf4c73548645
1package com.android.server.job;
2
3
4import android.content.ComponentName;
5import android.content.Context;
6import android.app.job.JobInfo;
7import android.app.job.JobInfo.Builder;
8import android.os.PersistableBundle;
9import android.os.SystemClock;
10import android.test.AndroidTestCase;
11import android.test.RenamingDelegatingContext;
12import android.util.Log;
13import android.util.ArraySet;
14
15import com.android.server.job.controllers.JobStatus;
16
17import java.util.Iterator;
18
19/**
20 * Test reading and writing correctly from file.
21 */
22public class JobStoreTest extends AndroidTestCase {
23    private static final String TAG = "TaskStoreTest";
24    private static final String TEST_PREFIX = "_test_";
25
26    private static final int SOME_UID = 34234;
27    private ComponentName mComponent;
28    private static final long IO_WAIT = 1000L;
29
30    JobStore mTaskStoreUnderTest;
31    Context mTestContext;
32
33    @Override
34    public void setUp() throws Exception {
35        mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
36        Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
37        mTaskStoreUnderTest =
38                JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
39        mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
40    }
41
42    @Override
43    public void tearDown() throws Exception {
44        mTaskStoreUnderTest.clear();
45    }
46
47    public void testMaybeWriteStatusToDisk() throws Exception {
48        int taskId = 5;
49        long runByMillis = 20000L; // 20s
50        long runFromMillis = 2000L; // 2s
51        long initialBackoff = 10000L; // 10s
52
53        final JobInfo task = new Builder(taskId, mComponent)
54                .setRequiresCharging(true)
55                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
56                .setBackoffCriteria(initialBackoff, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
57                .setOverrideDeadline(runByMillis)
58                .setMinimumLatency(runFromMillis)
59                .setPersisted(true)
60                .build();
61        final JobStatus ts = new JobStatus(task, SOME_UID);
62        mTaskStoreUnderTest.add(ts);
63        Thread.sleep(IO_WAIT);
64        // Manually load tasks from xml file.
65        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
66        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
67
68        assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
69        final JobStatus loadedTaskStatus = jobStatusSet.iterator().next();
70        assertTasksEqual(task, loadedTaskStatus.getJob());
71        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
72        assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
73        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
74                ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
75        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
76                ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
77
78    }
79
80    public void testWritingTwoFilesToDisk() throws Exception {
81        final JobInfo task1 = new Builder(8, mComponent)
82                .setRequiresDeviceIdle(true)
83                .setPeriodic(10000L)
84                .setRequiresCharging(true)
85                .setPersisted(true)
86                .build();
87        final JobInfo task2 = new Builder(12, mComponent)
88                .setMinimumLatency(5000L)
89                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
90                .setOverrideDeadline(30000L)
91                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
92                .setPersisted(true)
93                .build();
94        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
95        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
96        mTaskStoreUnderTest.add(taskStatus1);
97        mTaskStoreUnderTest.add(taskStatus2);
98        Thread.sleep(IO_WAIT);
99
100        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
101        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
102        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
103        Iterator<JobStatus> it = jobStatusSet.iterator();
104        JobStatus loaded1 = it.next();
105        JobStatus loaded2 = it.next();
106
107        // Reverse them so we know which comparison to make.
108        if (loaded1.getJobId() != 8) {
109            JobStatus tmp = loaded1;
110            loaded1 = loaded2;
111            loaded2 = tmp;
112        }
113
114        assertTasksEqual(task1, loaded1.getJob());
115        assertTasksEqual(task2, loaded2.getJob());
116        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
117        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
118        // Check that the loaded task has the correct runtimes.
119        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
120                taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
121        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
122                taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
123        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
124                taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
125        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
126                taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
127
128    }
129
130    public void testWritingTaskWithExtras() throws Exception {
131        JobInfo.Builder b = new Builder(8, mComponent)
132                .setRequiresDeviceIdle(true)
133                .setPeriodic(10000L)
134                .setRequiresCharging(true)
135                .setPersisted(true);
136
137        PersistableBundle extras = new PersistableBundle();
138        extras.putDouble("hello", 3.2);
139        extras.putString("hi", "there");
140        extras.putInt("into", 3);
141        b.setExtras(extras);
142        final JobInfo task = b.build();
143        JobStatus taskStatus = new JobStatus(task, SOME_UID);
144
145        mTaskStoreUnderTest.add(taskStatus);
146        Thread.sleep(IO_WAIT);
147
148        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
149        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
150        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
151        JobStatus loaded = jobStatusSet.iterator().next();
152        assertTasksEqual(task, loaded.getJob());
153    }
154
155    public void testMassivePeriodClampedOnRead() throws Exception {
156        final long TEN_SECONDS = 10000L;
157        JobInfo.Builder b = new Builder(8, mComponent)
158                .setPeriodic(TEN_SECONDS)
159                .setPersisted(true);
160        final long invalidLateRuntimeElapsedMillis =
161                SystemClock.elapsedRealtime() + (TEN_SECONDS * 2) + 5000;  // >2P from now.
162        final long invalidEarlyRuntimeElapsedMillis =
163                invalidLateRuntimeElapsedMillis - TEN_SECONDS;  // Early is (late - period).
164        final JobStatus js = new JobStatus(b.build(), SOME_UID,
165                invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis);
166
167        mTaskStoreUnderTest.add(js);
168        Thread.sleep(IO_WAIT);
169
170        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
171        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
172        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
173        JobStatus loaded = jobStatusSet.iterator().next();
174
175        // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll
176        // call SystemClock.elapsedRealtime after doing the disk i/o.
177        final long newNowElapsed = SystemClock.elapsedRealtime();
178        assertTrue("Early runtime wasn't correctly clamped.",
179                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
180        // Assert late runtime was clamped to be now + period*2.
181        assertTrue("Early runtime wasn't correctly clamped.",
182                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS*2);
183    }
184
185    /**
186     * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
187     */
188    private void assertTasksEqual(JobInfo first, JobInfo second) {
189        assertEquals("Different task ids.", first.getId(), second.getId());
190        assertEquals("Different components.", first.getService(), second.getService());
191        assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
192        assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
193        assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
194                second.getInitialBackoffMillis());
195        assertEquals("Different backoff policy.", first.getBackoffPolicy(),
196                second.getBackoffPolicy());
197
198        assertEquals("Invalid charging constraint.", first.isRequireCharging(),
199                second.isRequireCharging());
200        assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
201                second.isRequireDeviceIdle());
202        assertEquals("Invalid unmetered constraint.",
203                first.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED,
204                second.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED);
205        assertEquals("Invalid connectivity constraint.",
206                first.getNetworkType() == JobInfo.NETWORK_TYPE_ANY,
207                second.getNetworkType() == JobInfo.NETWORK_TYPE_ANY);
208        assertEquals("Invalid deadline constraint.",
209                first.hasLateConstraint(),
210                second.hasLateConstraint());
211        assertEquals("Invalid delay constraint.",
212                first.hasEarlyConstraint(),
213                second.hasEarlyConstraint());
214        assertEquals("Extras don't match",
215                first.getExtras().toString(), second.getExtras().toString());
216    }
217
218    /**
219     * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading
220     * the correct values), there is some latency involved that terrorises a naive assertEquals().
221     * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision
222     * more reasonable.
223     */
224    private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) {
225        final long DELTA_MILLIS = 700L;  // We allow up to 700ms of latency for IO read/writes.
226        assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT);
227    }
228
229    private static class StubClass {}
230
231}
232