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