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

Commit 3e6ee946 authored by Victor Hsieh's avatar Victor Hsieh
Browse files

Add tests to verify job scheduling on events

... of rebootless APEX and preload update installation.

Changes made to the actual service:
 * DO_BINARY_MEASUREMENTS_JOB_ID is now a constant, like most of jobs in
   the framework. This makes it easy to use from the host.
 * Replace the local state of sScheduled with a query to JobScheduler,
   which is the source of truth. I've messed up the state during test
   and manual interaction, and this is supposed to make it more
   resilient.

Bug: 265244016
Test: atest BinaryTransparencyHostTest
Test: atest BinaryTransparencyServiceTest
Change-Id: I17872bbded0b5c5c44e6fbd1c669f1eb00f3333e
parent a97f246b
Loading
Loading
Loading
Loading
+3 −8
Original line number Diff line number Diff line
@@ -96,7 +96,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
@@ -287,7 +286,7 @@ public class BinaryTransparencyService extends SystemService {
         * - dynamically installed mobile bundled apps (MBAs) (new in Android U)
         */
        public void recordMeasurementsForAllPackages() {
            // check if we should record the resulting measurements
            // check if we should measure and record
            long currentTimeMs = System.currentTimeMillis();
            if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
                Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
@@ -1227,10 +1226,8 @@ public class BinaryTransparencyService extends SystemService {
     * JobService to measure all covered binaries and record result to Westworld.
     */
    public static class UpdateMeasurementsJobService extends JobService {
        private static AtomicBoolean sScheduled = new AtomicBoolean();
        private static long sTimeLastRanMs = 0;
        private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
                UpdateMeasurementsJobService.class.hashCode();
        private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;

        @Override
        public boolean onStartJob(JobParameters params) {
@@ -1253,7 +1250,6 @@ public class BinaryTransparencyService extends SystemService {
                    return;
                }
                sTimeLastRanMs = System.currentTimeMillis();
                sScheduled.set(false);
                jobFinished(params, false);
            }).start();

@@ -1274,7 +1270,7 @@ public class BinaryTransparencyService extends SystemService {
                return;
            }

            if (sScheduled.get()) {
            if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
                Slog.d(TAG, "A measurement job has already been scheduled.");
                return;
            }
@@ -1300,7 +1296,6 @@ public class BinaryTransparencyService extends SystemService {
                Slog.e(TAG, "Failed to schedule job to measure binaries.");
                return;
            }
            sScheduled.set(true);
            Slog.d(TAG, TextUtils.formatSimple(
                    "Job %d to measure binaries was scheduled successfully.",
                    DO_BINARY_MEASUREMENTS_JOB_ID));
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ java_test_host {
    data: [
        ":BinaryTransparencyTestApp",
        ":EasterEgg",
        ":com.android.apex.cts.shim.v2_rebootless_prebuilt",
    ],
    test_suites: [
        "general-tests",
+74 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.transparency.test;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,14 +30,22 @@ import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.TimeUnit;

// TODO: Add @Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
    private static final String PACKAGE_NAME = "android.transparency.test.app";

    private static final String JOB_ID = "1740526926";

    /** Waiting time for the job to be scheduled */
    private static final int JOB_CREATION_MAX_SECONDS = 5;

    @After
    public void tearDown() throws Exception {
        uninstallPackage("com.android.egg");
        uninstallRebootlessApex();
    }

    @Test
@@ -63,6 +72,28 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
        runDeviceTest("testCollectAllUpdatedPreloadInfo");
    }

    @Test
    public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
        cancelPendingJob();
        installRebootlessApex();

        // Verify
        expectJobToBeScheduled();
        // Just cancel since we can't verifying very meaningfully.
        cancelPendingJob();
    }

    @Test
    public void testPreloadUpdateTriggersJobScheduling() throws Exception {
        cancelPendingJob();
        installPackage("EasterEgg.apk");

        // Verify
        expectJobToBeScheduled();
        // Just cancel since we can't verifying very meaningfully.
        cancelPendingJob();
    }

    @Test
    public void testMeasureMbas() throws Exception {
        // TODO(265244016): figure out a way to install an MBA
@@ -74,4 +105,47 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
        options.setTestMethodName(method);
        runDeviceTests(options);
    }

    private void cancelPendingJob() throws DeviceNotAvailableException {
        CommandResult result = getDevice().executeShellV2Command(
                "cmd jobscheduler cancel android " + JOB_ID);
        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
    }

    private void expectJobToBeScheduled() throws Exception {
        for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) {
            CommandResult result = getDevice().executeShellV2Command(
                    "cmd jobscheduler get-job-state android " + JOB_ID);
            String state = result.getStdout().toString();
            if (state.startsWith("unknown")) {
                // The job hasn't been scheduled yet. So try again.
                TimeUnit.SECONDS.sleep(1);
            } else if (result.getExitCode() != 0) {
                fail("Failing due to unexpected job state: " + result);
            } else {
                // The job exists, which is all we care about here
                return;
            }
        }
        fail("Timed out waiting for the job to be scheduled");
    }

    private void installRebootlessApex() throws Exception {
        installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
    }

    private void uninstallRebootlessApex() throws DeviceNotAvailableException {
        // Reboot only if the APEX is not the pre-install one.
        CommandResult result = getDevice().executeShellV2Command(
                "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
        if (result.getStdout().contains("/data/apex/active/")) {
            uninstallPackage("com.android.apex.cts.shim");
            getDevice().reboot();

            // Reboot enforces SELinux. Make it permissive again.
            CommandResult runResult = getDevice().executeShellV2Command("setenforce 0");
            assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
        }
    }
}