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