Loading apex/jobscheduler/service/java/com/android/server/job/JobStore.java +59 −1 Original line number Diff line number Diff line Loading @@ -91,8 +91,11 @@ public final class JobStore { /** Threshold to adjust how often we want to write to the db. */ private static final long JOB_PERSIST_DELAY = 2000L; private static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; @VisibleForTesting static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; private static final int ALL_UIDS = -1; @VisibleForTesting static final int INVALID_UID = -2; final Object mLock; final Object mWriteScheduleLock; // used solely for invariants around write scheduling Loading Loading @@ -529,6 +532,25 @@ public final class JobStore { return values; } @VisibleForTesting static int extractUidFromJobFileName(@NonNull File file) { final String fileName = file.getName(); if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) { try { final int subEnd = fileName.length() - 4; // -4 for ".xml" final int uid = Integer.parseInt( fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd)); if (uid < 0) { return INVALID_UID; } return uid; } catch (Exception e) { Slog.e(TAG, "Unexpected file name format", e); } } return INVALID_UID; } /** * Runnable that writes {@link #mJobSet} out to xml. * NOTE: This Runnable locks on mLock Loading @@ -543,6 +565,42 @@ public final class JobStore { private void prepare() { mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS); if (mUseSplitFiles) { // Put the set of changed UIDs in the copy list so that we update each file, // especially if we've dropped all jobs for that UID. if (mPendingJobWriteUids.get(ALL_UIDS)) { // ALL_UIDS is only used when we switch file splitting policy or for tests, // so going through the file list here shouldn't be // a large performance hit on user devices. final File[] files; try { files = mJobFileDirectory.listFiles(); } catch (SecurityException e) { Slog.wtf(TAG, "Not allowed to read job file directory", e); return; } if (files == null) { Slog.wtfStack(TAG, "Couldn't get job file list"); } else { for (File file : files) { final int uid = extractUidFromJobFileName(file); if (uid != INVALID_UID) { mJobStoreCopy.put(uid, new ArrayList<>()); } } } } else { for (int i = 0; i < mPendingJobWriteUids.size(); ++i) { mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>()); } } } else { // Single file mode. // Put the catchall UID in the copy list so that we update the single file, // especially if we've dropped all persisted jobs. mJobStoreCopy.put(ALL_UIDS, new ArrayList<>()); } } @Override Loading services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +174 −20 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -22,6 +23,7 @@ import android.os.Build; import android.os.PersistableBundle; import android.os.SystemClock; import android.test.RenamingDelegatingContext; import android.util.ArraySet; import android.util.Log; import android.util.Pair; Loading @@ -38,9 +40,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.time.Clock; import java.time.ZoneOffset; import java.util.Iterator; /** * Test reading and writing correctly from file. Loading Loading @@ -93,11 +95,147 @@ public class JobStoreTest { mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L); } private void setUseSplitFiles(boolean useSplitFiles) throws Exception { mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles); waitForPendingIo(); } private void waitForPendingIo() throws Exception { assertTrue("Timed out waiting for persistence I/O to complete", mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L)); } /** Test that we properly remove the last job of an app from the persisted file. */ @Test public void testRemovingLastJob_singleFile() throws Exception { setUseSplitFiles(false); runRemovingLastJob(); } /** Test that we properly remove the last job of an app from the persisted file. */ @Test public void testRemovingLastJob_splitFiles() throws Exception { setUseSplitFiles(true); runRemovingLastJob(); } private void runRemovingLastJob() throws Exception { final JobInfo task1 = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) .setRequiresCharging(true) .setPersisted(true) .build(); final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) .setOverrideDeadline(30000L) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); final int uid1 = SOME_UID; final int uid2 = uid1 + 1; final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); runWritingJobsToDisk(JobStatus1, JobStatus2); // Remove 1 job mTaskStoreUnderTest.remove(JobStatus1, true); waitForPendingIo(); JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); assertJobsEqual(JobStatus2, loaded); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2)); // Remove 2nd job mTaskStoreUnderTest.remove(JobStatus2, true); waitForPendingIo(); jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); } /** Test that we properly clear the persisted file when all jobs are dropped. */ @Test public void testClearJobs_singleFile() throws Exception { setUseSplitFiles(false); runClearJobs(); } /** Test that we properly clear the persisted file when all jobs are dropped. */ @Test public void testClearJobs_splitFiles() throws Exception { setUseSplitFiles(true); runClearJobs(); } private void runClearJobs() throws Exception { final JobInfo task1 = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) .setRequiresCharging(true) .setPersisted(true) .build(); final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) .setOverrideDeadline(30000L) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); final int uid1 = SOME_UID; final int uid2 = uid1 + 1; final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); runWritingJobsToDisk(JobStatus1, JobStatus2); // Remove all jobs mTaskStoreUnderTest.clear(); waitForPendingIo(); JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); } @Test public void testExtractUidFromJobFileName() { File file = new File(mTestContext.getFilesDir(), "randomName"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "jobs.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), ".xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "1000.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "10000"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml"); assertEquals(1, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml"); assertEquals(101023, JobStore.extractUidFromJobFileName(file)); } @Test public void testStringToIntArrayAndIntArrayToString() { final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 }; Loading Loading @@ -144,13 +282,13 @@ public class JobStoreTest { @Test public void testWritingTwoJobsToDisk_singleFile() throws Exception { mTaskStoreUnderTest.setUseSplitFiles(false); setUseSplitFiles(false); runWritingTwoJobsToDisk(); } @Test public void testWritingTwoJobsToDisk_splitFiles() throws Exception { mTaskStoreUnderTest.setUseSplitFiles(true); setUseSplitFiles(true); runWritingTwoJobsToDisk(); } Loading @@ -172,28 +310,44 @@ public class JobStoreTest { final int uid2 = uid1 + 1; final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); mTaskStoreUnderTest.add(taskStatus1); mTaskStoreUnderTest.add(taskStatus2); runWritingJobsToDisk(taskStatus1, taskStatus2); } private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception { ArraySet<JobStatus> expectedJobs = new ArraySet<>(); for (JobStatus jobStatus : jobStatuses) { mTaskStoreUnderTest.add(jobStatus); expectedJobs.add(jobStatus); } waitForPendingIo(); final JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size()); Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator(); JobStatus loaded1 = it.next(); JobStatus loaded2 = it.next(); // Reverse them so we know which comparison to make. if (loaded1.getJobId() != 8) { JobStatus tmp = loaded1; loaded1 = loaded2; loaded2 = tmp; } assertJobsEqual(taskStatus1, loaded1); assertJobsEqual(taskStatus2, loaded2); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1)); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2)); assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size()); int count = 0; final int expectedCount = expectedJobs.size(); for (JobStatus loaded : jobStatusSet.getAllJobs()) { count++; for (int i = 0; i < expectedJobs.size(); ++i) { JobStatus expected = expectedJobs.valueAt(i); try { assertJobsEqual(expected, loaded); expectedJobs.remove(expected); break; } catch (AssertionError e) { // Not equal. Move along. } } } assertEquals("Loaded more jobs than expected", expectedCount, count); if (expectedJobs.size() > 0) { fail("Not all expected jobs were restored"); } for (JobStatus jobStatus : jobStatuses) { assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus)); } } @Test Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobStore.java +59 −1 Original line number Diff line number Diff line Loading @@ -91,8 +91,11 @@ public final class JobStore { /** Threshold to adjust how often we want to write to the db. */ private static final long JOB_PERSIST_DELAY = 2000L; private static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; @VisibleForTesting static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; private static final int ALL_UIDS = -1; @VisibleForTesting static final int INVALID_UID = -2; final Object mLock; final Object mWriteScheduleLock; // used solely for invariants around write scheduling Loading Loading @@ -529,6 +532,25 @@ public final class JobStore { return values; } @VisibleForTesting static int extractUidFromJobFileName(@NonNull File file) { final String fileName = file.getName(); if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) { try { final int subEnd = fileName.length() - 4; // -4 for ".xml" final int uid = Integer.parseInt( fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd)); if (uid < 0) { return INVALID_UID; } return uid; } catch (Exception e) { Slog.e(TAG, "Unexpected file name format", e); } } return INVALID_UID; } /** * Runnable that writes {@link #mJobSet} out to xml. * NOTE: This Runnable locks on mLock Loading @@ -543,6 +565,42 @@ public final class JobStore { private void prepare() { mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS); if (mUseSplitFiles) { // Put the set of changed UIDs in the copy list so that we update each file, // especially if we've dropped all jobs for that UID. if (mPendingJobWriteUids.get(ALL_UIDS)) { // ALL_UIDS is only used when we switch file splitting policy or for tests, // so going through the file list here shouldn't be // a large performance hit on user devices. final File[] files; try { files = mJobFileDirectory.listFiles(); } catch (SecurityException e) { Slog.wtf(TAG, "Not allowed to read job file directory", e); return; } if (files == null) { Slog.wtfStack(TAG, "Couldn't get job file list"); } else { for (File file : files) { final int uid = extractUidFromJobFileName(file); if (uid != INVALID_UID) { mJobStoreCopy.put(uid, new ArrayList<>()); } } } } else { for (int i = 0; i < mPendingJobWriteUids.size(); ++i) { mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>()); } } } else { // Single file mode. // Put the catchall UID in the copy list so that we update the single file, // especially if we've dropped all persisted jobs. mJobStoreCopy.put(ALL_UIDS, new ArrayList<>()); } } @Override Loading
services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +174 −20 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -22,6 +23,7 @@ import android.os.Build; import android.os.PersistableBundle; import android.os.SystemClock; import android.test.RenamingDelegatingContext; import android.util.ArraySet; import android.util.Log; import android.util.Pair; Loading @@ -38,9 +40,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.time.Clock; import java.time.ZoneOffset; import java.util.Iterator; /** * Test reading and writing correctly from file. Loading Loading @@ -93,11 +95,147 @@ public class JobStoreTest { mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L); } private void setUseSplitFiles(boolean useSplitFiles) throws Exception { mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles); waitForPendingIo(); } private void waitForPendingIo() throws Exception { assertTrue("Timed out waiting for persistence I/O to complete", mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L)); } /** Test that we properly remove the last job of an app from the persisted file. */ @Test public void testRemovingLastJob_singleFile() throws Exception { setUseSplitFiles(false); runRemovingLastJob(); } /** Test that we properly remove the last job of an app from the persisted file. */ @Test public void testRemovingLastJob_splitFiles() throws Exception { setUseSplitFiles(true); runRemovingLastJob(); } private void runRemovingLastJob() throws Exception { final JobInfo task1 = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) .setRequiresCharging(true) .setPersisted(true) .build(); final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) .setOverrideDeadline(30000L) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); final int uid1 = SOME_UID; final int uid2 = uid1 + 1; final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); runWritingJobsToDisk(JobStatus1, JobStatus2); // Remove 1 job mTaskStoreUnderTest.remove(JobStatus1, true); waitForPendingIo(); JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); assertJobsEqual(JobStatus2, loaded); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2)); // Remove 2nd job mTaskStoreUnderTest.remove(JobStatus2, true); waitForPendingIo(); jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); } /** Test that we properly clear the persisted file when all jobs are dropped. */ @Test public void testClearJobs_singleFile() throws Exception { setUseSplitFiles(false); runClearJobs(); } /** Test that we properly clear the persisted file when all jobs are dropped. */ @Test public void testClearJobs_splitFiles() throws Exception { setUseSplitFiles(true); runClearJobs(); } private void runClearJobs() throws Exception { final JobInfo task1 = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) .setRequiresCharging(true) .setPersisted(true) .build(); final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) .setOverrideDeadline(30000L) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); final int uid1 = SOME_UID; final int uid2 = uid1 + 1; final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); runWritingJobsToDisk(JobStatus1, JobStatus2); // Remove all jobs mTaskStoreUnderTest.clear(); waitForPendingIo(); JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); } @Test public void testExtractUidFromJobFileName() { File file = new File(mTestContext.getFilesDir(), "randomName"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "jobs.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), ".xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "1000.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), "10000"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml"); assertEquals(1, JobStore.extractUidFromJobFileName(file)); file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml"); assertEquals(101023, JobStore.extractUidFromJobFileName(file)); } @Test public void testStringToIntArrayAndIntArrayToString() { final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 }; Loading Loading @@ -144,13 +282,13 @@ public class JobStoreTest { @Test public void testWritingTwoJobsToDisk_singleFile() throws Exception { mTaskStoreUnderTest.setUseSplitFiles(false); setUseSplitFiles(false); runWritingTwoJobsToDisk(); } @Test public void testWritingTwoJobsToDisk_splitFiles() throws Exception { mTaskStoreUnderTest.setUseSplitFiles(true); setUseSplitFiles(true); runWritingTwoJobsToDisk(); } Loading @@ -172,28 +310,44 @@ public class JobStoreTest { final int uid2 = uid1 + 1; final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); mTaskStoreUnderTest.add(taskStatus1); mTaskStoreUnderTest.add(taskStatus2); runWritingJobsToDisk(taskStatus1, taskStatus2); } private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception { ArraySet<JobStatus> expectedJobs = new ArraySet<>(); for (JobStatus jobStatus : jobStatuses) { mTaskStoreUnderTest.add(jobStatus); expectedJobs.add(jobStatus); } waitForPendingIo(); final JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size()); Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator(); JobStatus loaded1 = it.next(); JobStatus loaded2 = it.next(); // Reverse them so we know which comparison to make. if (loaded1.getJobId() != 8) { JobStatus tmp = loaded1; loaded1 = loaded2; loaded2 = tmp; } assertJobsEqual(taskStatus1, loaded1); assertJobsEqual(taskStatus2, loaded2); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1)); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2)); assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size()); int count = 0; final int expectedCount = expectedJobs.size(); for (JobStatus loaded : jobStatusSet.getAllJobs()) { count++; for (int i = 0; i < expectedJobs.size(); ++i) { JobStatus expected = expectedJobs.valueAt(i); try { assertJobsEqual(expected, loaded); expectedJobs.remove(expected); break; } catch (AssertionError e) { // Not equal. Move along. } } } assertEquals("Loaded more jobs than expected", expectedCount, count); if (expectedJobs.size() > 0) { fail("Not all expected jobs were restored"); } for (JobStatus jobStatus : jobStatuses) { assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus)); } } @Test Loading