1 package com.android.server.job;
2 
3 
4 import android.content.ComponentName;
5 import android.content.Context;
6 import android.app.job.JobInfo;
7 import android.app.job.JobInfo.Builder;
8 import android.os.PersistableBundle;
9 import android.test.AndroidTestCase;
10 import android.test.RenamingDelegatingContext;
11 import android.util.Log;
12 import android.util.ArraySet;
13 
14 import com.android.server.job.controllers.JobStatus;
15 
16 import java.util.Iterator;
17 
18 /**
19  * Test reading and writing correctly from file.
20  */
21 public 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
setUp()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
tearDown()42     public void tearDown() throws Exception {
43         mTaskStoreUnderTest.clear();
44     }
45 
testMaybeWriteStatusToDisk()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 
testWritingTwoFilesToDisk()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 
testWritingTaskWithExtras()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      */
assertTasksEqual(JobInfo first, JobInfo second)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      */
compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2)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