JobStoreTest.java revision d1c06753d045ad10e00e7aba53ee2adba0712ccc
101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williamspackage com.android.server.job;
23d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
33d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
43d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.content.ComponentName;
53d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.content.Context;
67060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tateimport android.app.job.JobInfo;
77060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tateimport android.app.job.JobInfo.Builder;
83d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.os.PersistableBundle;
93d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.test.AndroidTestCase;
103d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.test.RenamingDelegatingContext;
113d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williamsimport android.util.Log;
1201ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williamsimport android.util.ArraySet;
133d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
147060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tateimport com.android.server.job.controllers.JobStatus;
153d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1601ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williamsimport java.util.Iterator;
173d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
183d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams/**
193d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams * Test reading and writing correctly from file.
203d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams */
2101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williamspublic class JobStoreTest extends AndroidTestCase {
223d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private static final String TAG = "TaskStoreTest";
233d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private static final String TEST_PREFIX = "_test_";
2401ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams
253d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private static final int SOME_UID = 34234;
263d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private ComponentName mComponent;
2701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams    private static final long IO_WAIT = 1000L;
283d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
297060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate    JobStore mTaskStoreUnderTest;
303d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    Context mTestContext;
313d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
323d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    @Override
333d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    public void setUp() throws Exception {
343d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
353d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
3601ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        mTaskStoreUnderTest =
3701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
383d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
393d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
403d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
413d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    @Override
423d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    public void tearDown() throws Exception {
433d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTaskStoreUnderTest.clear();
443d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
453d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
463d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    public void testMaybeWriteStatusToDisk() throws Exception {
473d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        int taskId = 5;
483d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        long runByMillis = 20000L; // 20s
493d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        long runFromMillis = 2000L; // 2s
503d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        long initialBackoff = 10000L; // 10s
513d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
527060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate        final JobInfo task = new Builder(taskId, mComponent)
533d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setRequiresCharging(true)
54d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
55d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setBackoffCriteria(initialBackoff, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
563d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setOverrideDeadline(runByMillis)
573d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setMinimumLatency(runFromMillis)
58d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setPersisted(true)
593d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .build();
60900c67fc51fc2672458dd1c9641250f2ecc01a31Matthew Williams        final JobStatus ts = new JobStatus(task, SOME_UID);
613d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTaskStoreUnderTest.add(ts);
623d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        Thread.sleep(IO_WAIT);
633d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        // Manually load tasks from xml file.
6401ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
6501ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
6601ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams
6701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
6801ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        final JobStatus loadedTaskStatus = jobStatusSet.iterator().next();
6901ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertTasksEqual(task, loadedTaskStatus.getJob());
7001ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
7101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
7201ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
7301ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
7401ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
753d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
763d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
773d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
783d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    public void testWritingTwoFilesToDisk() throws Exception {
797060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate        final JobInfo task1 = new Builder(8, mComponent)
803d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setRequiresDeviceIdle(true)
813d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setPeriodic(10000L)
823d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setRequiresCharging(true)
83d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setPersisted(true)
843d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .build();
857060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate        final JobInfo task2 = new Builder(12, mComponent)
863d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setMinimumLatency(5000L)
87d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
883d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setOverrideDeadline(30000L)
89d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
90d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setPersisted(true)
913d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .build();
92900c67fc51fc2672458dd1c9641250f2ecc01a31Matthew Williams        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
93900c67fc51fc2672458dd1c9641250f2ecc01a31Matthew Williams        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
943d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTaskStoreUnderTest.add(taskStatus1);
953d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTaskStoreUnderTest.add(taskStatus2);
963d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        Thread.sleep(IO_WAIT);
9701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams
9801ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
9901ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
10001ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
10101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        Iterator<JobStatus> it = jobStatusSet.iterator();
10201ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        JobStatus loaded1 = it.next();
10301ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        JobStatus loaded2 = it.next();
10401ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertTasksEqual(task1, loaded1.getJob());
10501ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertTasksEqual(task2, loaded2.getJob());
10601ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams
10701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        // Check that the loaded task has the correct runtimes.
10801ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
10901ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
11001ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
11101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
11201ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
11301ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
11401ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
11501ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams                taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
1163d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1173d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
1183d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1193d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    public void testWritingTaskWithExtras() throws Exception {
1207060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate        JobInfo.Builder b = new Builder(8, mComponent)
1213d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setRequiresDeviceIdle(true)
1223d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                .setPeriodic(10000L)
123900c67fc51fc2672458dd1c9641250f2ecc01a31Matthew Williams                .setRequiresCharging(true)
124d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                .setPersisted(true);
1253d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1263d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        PersistableBundle extras = new PersistableBundle();
1273d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        extras.putDouble("hello", 3.2);
1283d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        extras.putString("hi", "there");
1293d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        extras.putInt("into", 3);
1303d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        b.setExtras(extras);
1317060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate        final JobInfo task = b.build();
132900c67fc51fc2672458dd1c9641250f2ecc01a31Matthew Williams        JobStatus taskStatus = new JobStatus(task, SOME_UID);
1333d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1343d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        mTaskStoreUnderTest.add(taskStatus);
1353d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        Thread.sleep(IO_WAIT);
1363d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
13701ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
13801ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
13901ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
14001ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        JobStatus loaded = jobStatusSet.iterator().next();
14101ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams        assertTasksEqual(task, loaded.getJob());
1423d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
1433d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1443d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    /**
1453d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
1463d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     */
1477060b04f6d92351b67222e636ab378a0273bf3e7Christopher Tate    private void assertTasksEqual(JobInfo first, JobInfo second) {
1483d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different task ids.", first.getId(), second.getId());
1493d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different components.", first.getService(), second.getService());
1503d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
1513d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
1523d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
1533d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.getInitialBackoffMillis());
1543d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Different backoff policy.", first.getBackoffPolicy(),
1553d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.getBackoffPolicy());
1563d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1573d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid charging constraint.", first.isRequireCharging(),
1583d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.isRequireCharging());
1593d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
1603d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.isRequireDeviceIdle());
1613d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid unmetered constraint.",
162d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                first.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED,
163d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                second.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED);
1643d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid connectivity constraint.",
165d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                first.getNetworkType() == JobInfo.NETWORK_TYPE_ANY,
166d1c06753d045ad10e00e7aba53ee2adba0712cccMatthew Williams                second.getNetworkType() == JobInfo.NETWORK_TYPE_ANY);
1673d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid deadline constraint.",
1683d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                first.hasLateConstraint(),
1693d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.hasLateConstraint());
1703d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Invalid delay constraint.",
1713d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                first.hasEarlyConstraint(),
1723d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                second.hasEarlyConstraint());
1733d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertEquals("Extras don't match",
1743d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams                first.getExtras().toString(), second.getExtras().toString());
1753d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
1763d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1773d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    /**
1783d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading
1793d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     * the correct values), there is some latency involved that terrorises a naive assertEquals().
1803d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision
1813d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     * more reasonable.
1823d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams     */
1833d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) {
1843d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        final long DELTA_MILLIS = 700L;  // We allow up to 700ms of latency for IO read/writes.
1853d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams        assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT);
1863d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    }
1873d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
1883d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams    private static class StubClass {}
1893d86fd2bb9db6067c49634bc4c6cdb4d5235ad36Matthew Williams
19001ac45b6ff2334925c8d24b5278b44e5e30f5622Matthew Williams}
191