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

Commit c5e2e803 authored by Richard Uhler's avatar Richard Uhler
Browse files

Create basic test for rollback of staged install.

The new test tests rollback of a staged install involving a single apk.

Test: atest StagedRollbackTest
Bug: 124043688
Bug: 124221060
Bug: 124284714

Change-Id: Ie125d44c4d30d142ee32beac4c42e735a47ec71e
parent 00baa4b4
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -21,5 +21,9 @@
    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
        <option name="package" value="com.android.tests.rollback" />
        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />

        <!-- Exclude the StagedRollbackTest tests, which needs to be specially
             driven from the StagedRollbackTest host test -->
        <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
    </test>
</configuration>
+4 −49
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.tests.rollback;

import static com.android.tests.rollback.RollbackTestUtils.assertPackageRollbackInfoEquals;
import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;

import android.Manifest;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -24,7 +28,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Handler;
@@ -42,7 +45,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
@@ -617,17 +619,6 @@ public class RollbackTest {
        }
    }

    // Helper function to test the value of a PackageRollbackInfo
    private void assertPackageRollbackInfoEquals(String packageName,
            long versionRolledBackFrom, long versionRolledBackTo,
            PackageRollbackInfo info) {
        assertEquals(packageName, info.getPackageName());
        assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
        assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
        assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
        assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
    }

    /**
     * Test bad update automatic rollback.
     */
@@ -713,23 +704,6 @@ public class RollbackTest {
        }
    }

    // Helper function to test the value of a RollbackInfo with single package
    private void assertRollbackInfoEquals(String packageName,
            long versionRolledBackFrom, long versionRolledBackTo,
            RollbackInfo info, VersionedPackage... causePackages) {
        assertNotNull(info);
        assertEquals(1, info.getPackages().size());
        assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
                info.getPackages().get(0));
        assertEquals(causePackages.length, info.getCausePackages().size());
        for (int i = 0; i < causePackages.length; ++i) {
            assertEquals(causePackages[i].getPackageName(),
                    info.getCausePackages().get(i).getPackageName());
            assertEquals(causePackages[i].getLongVersionCode(),
                    info.getCausePackages().get(i).getLongVersionCode());
        }
    }

    // Helper function to test that the given rollback info is a rollback for
    // the atomic set {A2, B2} -> {A1, B1}.
    private void assertRollbackInfoForAandB(RollbackInfo rollback) {
@@ -743,23 +717,4 @@ public class RollbackTest {
            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
        }
    }

    // Helper function to return the RollbackInfo with a given package in the
    // list of rollbacks. Throws an assertion failure if there is more than
    // one such rollback info. Returns null if there are no such rollback
    // infos.
    private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
            String packageName) {
        RollbackInfo found = null;
        for (RollbackInfo rollback : rollbacks) {
            for (PackageRollbackInfo info : rollback.getPackages()) {
                if (packageName.equals(info.getPackageName())) {
                    assertNull(found);
                    found = rollback;
                    break;
                }
            }
        }
        return found;
    }
}
+155 −2
Original line number Diff line number Diff line
@@ -16,19 +16,31 @@

package com.android.tests.rollback;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.support.test.InstrumentationRegistry;
import android.util.Log;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Utilities to facilitate testing rollbacks.
@@ -157,20 +169,28 @@ class RollbackTestUtils {

    /**
     * Installs the apks with the given resource names as an atomic set.
     * <p>
     * In case of staged installs, this function will return succesfully after
     * the staged install has been committed and is ready for the device to
     * reboot.
     *
     * @param staged if the rollback should be staged.
     * @param enableRollback if rollback should be enabled.
     * @param resourceNames names of the class loader resource for the apks to
     *        install.
     * @throws AssertionError if the installation fails.
     */
    static void installMultiPackage(boolean enableRollback, String... resourceNames)
            throws InterruptedException, IOException {
    private static void install(boolean staged, boolean enableRollback,
            String... resourceNames) throws InterruptedException, IOException {
        Context context = InstrumentationRegistry.getContext();
        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();

        PackageInstaller.SessionParams multiPackageParams = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        multiPackageParams.setMultiPackage();
        if (staged) {
            multiPackageParams.setStaged();
        }
        if (enableRollback) {
            // TODO: Do we set this on the parent params, the child params, or
            // both?
@@ -183,6 +203,9 @@ class RollbackTestUtils {
            PackageInstaller.Session session = null;
            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            if (staged) {
                params.setStaged();
            }
            if (enableRollback) {
                params.setEnableRollback();
            }
@@ -204,6 +227,36 @@ class RollbackTestUtils {
        // Commit the session (this will start the installation workflow).
        multiPackage.commit(LocalIntentSender.getIntentSender());
        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());

        if (staged) {
            waitForSessionReady(multiPackageId);
        }
    }

    /**
     * Installs the apks with the given resource names as an atomic set.
     *
     * @param enableRollback if rollback should be enabled.
     * @param resourceNames names of the class loader resource for the apks to
     *        install.
     * @throws AssertionError if the installation fails.
     */
    static void installMultiPackage(boolean enableRollback, String... resourceNames)
            throws InterruptedException, IOException {
        install(false, enableRollback, resourceNames);
    }

    /**
     * Installs the apks with the given resource names as a staged atomic set.
     *
     * @param enableRollback if rollback should be enabled.
     * @param resourceNames names of the class loader resource for the apks to
     *        install.
     * @throws AssertionError if the installation fails.
     */
    static void installStaged(boolean enableRollback, String... resourceNames)
            throws InterruptedException, IOException {
        install(true, enableRollback, resourceNames);
    }

    static void adoptShellPermissionIdentity(String... permissions) {
@@ -219,4 +272,104 @@ class RollbackTestUtils {
            .getUiAutomation()
            .dropShellPermissionIdentity();
    }

    /**
     * Returns the RollbackInfo with a given package in the list of rollbacks.
     * Throws an assertion failure if there is more than one such rollback
     * info. Returns null if there are no such rollback infos.
     */
    static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
            String packageName) {
        RollbackInfo found = null;
        for (RollbackInfo rollback : rollbacks) {
            for (PackageRollbackInfo info : rollback.getPackages()) {
                if (packageName.equals(info.getPackageName())) {
                    assertNull(found);
                    found = rollback;
                    break;
                }
            }
        }
        return found;
    }

    /**
     * Asserts that the given PackageRollbackInfo has the expected package
     * name and versions.
     */
    static void assertPackageRollbackInfoEquals(String packageName,
            long versionRolledBackFrom, long versionRolledBackTo,
            PackageRollbackInfo info) {
        assertEquals(packageName, info.getPackageName());
        assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
        assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
        assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
        assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
    }

    /**
     * Asserts that the given RollbackInfo has a single package with expected
     * package name and versions.
     */
    static void assertRollbackInfoEquals(String packageName,
            long versionRolledBackFrom, long versionRolledBackTo,
            RollbackInfo info, VersionedPackage... causePackages) {
        assertNotNull(info);
        assertEquals(1, info.getPackages().size());
        assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
                info.getPackages().get(0));
        assertEquals(causePackages.length, info.getCausePackages().size());
        for (int i = 0; i < causePackages.length; ++i) {
            assertEquals(causePackages[i].getPackageName(),
                    info.getCausePackages().get(i).getPackageName());
            assertEquals(causePackages[i].getLongVersionCode(),
                    info.getCausePackages().get(i).getLongVersionCode());
        }
    }

    /**
     * Waits for the given session to be marked as ready.
     * Throws an assertion if the session fails.
     */
    static void waitForSessionReady(int sessionId) {
        BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
        BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                PackageInstaller.SessionInfo info =
                        intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
                if (info != null && info.getSessionId() == sessionId) {
                    if (info.isSessionReady() || info.isSessionFailed()) {
                        try {
                            sessionStatus.put(info);
                        } catch (InterruptedException e) {
                            Log.e(TAG, "Failed to put session info.", e);
                        }
                    }
                }
            }
        };
        IntentFilter sessionUpdatedFilter =
                new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);

        Context context = InstrumentationRegistry.getContext();
        context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);

        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
        PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);

        try {
            if (info.isSessionReady() || info.isSessionFailed()) {
                sessionStatus.put(info);
            }

            info = sessionStatus.take();
            context.unregisterReceiver(sessionUpdatedReceiver);
            if (info.isSessionFailed()) {
                throw new AssertionError(info.getStagedSessionErrorMessage());
            }
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        }
    }
}
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tests.rollback;

import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;

import android.Manifest;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Tests for rollback of staged installs.
 * <p>
 * Note: These tests require reboot in between test phases. They are run
 * specially so that the testFooEnableRollback, testFooCommitRollback, and
 * testFooConfirmRollback phases of each test are run in order with reboots in
 * between them.
 */
@RunWith(JUnit4.class)
public class StagedRollbackTest {

    private static final String TAG = "RollbackTest";
    private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";

    /**
     * Adopts common shell permissions needed for rollback tests.
     */
    @Before
    public void adoptShellPermissions() {
        RollbackTestUtils.adoptShellPermissionIdentity(
                Manifest.permission.INSTALL_PACKAGES,
                Manifest.permission.DELETE_PACKAGES,
                Manifest.permission.MANAGE_ROLLBACKS);
    }

    /**
     * Drops shell permissions needed for rollback tests.
     */
    @After
    public void dropShellPermissions() {
        RollbackTestUtils.dropShellPermissionIdentity();
    }


    /**
     * Test basic rollbacks. Enable rollback phase.
     */
    @Test
    public void testBasicEnableRollback() throws Exception {
        RollbackTestUtils.uninstall(TEST_APP_A);
        assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));

        RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
        assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));

        RollbackTestUtils.installStaged(true, "RollbackTestAppAv2.apk");

        // At this point, the host test driver will reboot the device and run
        // testBasicCommitRollback().
    }

    /**
     * Test basic rollbacks. Commit rollback phase.
     */
    @Test
    public void testBasicCommitRollback() throws Exception {
        assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));

        RollbackManager rm = RollbackTestUtils.getRollbackManager();
        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
                rm.getAvailableRollbacks(), TEST_APP_A);
        assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
        assertTrue(rollback.isStaged());

        RollbackTestUtils.rollback(rollback.getRollbackId());

        rollback = getUniqueRollbackInfoForPackage(
                rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
        assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
        assertTrue(rollback.isStaged());
        assertNotEquals(-1, rollback.getCommittedSessionId());

        RollbackTestUtils.waitForSessionReady(rollback.getCommittedSessionId());

        // The app should not be rolled back until after reboot.
        assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));

        // At this point, the host test driver will reboot the device and run
        // testBasicConfirmRollback().
    }

    /**
     * Test basic rollbacks. Confirm rollback phase.
     */
    @Test
    public void testBasicConfirmRollback() throws Exception {
        assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));

        RollbackManager rm = RollbackTestUtils.getRollbackManager();
        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
                rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
        assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
        assertTrue(rollback.isStaged());
        assertNotEquals(-1, rollback.getCommittedSessionId());
    }
}
+20 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.tests.rollback.host;

import static org.junit.Assert.assertTrue;

import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;

@@ -28,11 +30,27 @@ import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
public class StagedRollbackTest extends BaseHostJUnit4Test {

    /**
     * Runs the given phase of a test by calling into the device.
     * Throws an exception if the test phase fails.
     * <p>
     * For example, <code>runPhase("testBasicEnableRollback");</code>
     */
    private void runPhase(String phase) throws Exception {
        assertTrue(runDeviceTests("com.android.tests.rollback",
                    "com.android.tests.rollback.StagedRollbackTest",
                    phase));
    }

    /**
     * Tests staged rollbacks.
     */
    @Test
    public void testStagedRollback() {
        // TODO: Actually test staged rollbacks.
    public void testBasic() throws Exception {
        runPhase("testBasicEnableRollback");
        getDevice().reboot();
        runPhase("testBasicCommitRollback");
        getDevice().reboot();
        runPhase("testBasicConfirmRollback");
    }
}