Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b54bd67c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update file when all persisted jobs are removed."

parents 16344766 b3047e56
Loading
Loading
Loading
Loading
+59 −1
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
+174 −20
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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.
@@ -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 };
@@ -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();
    }

@@ -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