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