diff --git a/tests/RollbackTest/Android.mk b/tests/RollbackTest/Android.mk index 40d4eff535774..0967ad3767c63 100644 --- a/tests/RollbackTest/Android.mk +++ b/tests/RollbackTest/Android.mk @@ -71,7 +71,7 @@ ROLLBACK_TEST_APP_BV2 := $(LOCAL_INSTALLED_MODULE) # RollbackTest include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES := $(call all-java-files-under, RollbackTest/src) LOCAL_PACKAGE_NAME := RollbackTest LOCAL_MODULE_TAGS := tests LOCAL_STATIC_JAVA_LIBRARIES := android-support-test @@ -84,10 +84,21 @@ LOCAL_JAVA_RESOURCE_FILES := \ $(ROLLBACK_TEST_APP_BV1) \ $(ROLLBACK_TEST_APP_BV2) \ $(ROLLBACK_TEST_APEX_V2) +LOCAL_MANIFEST_FILE := RollbackTest/AndroidManifest.xml LOCAL_SDK_VERSION := system_current LOCAL_TEST_CONFIG := RollbackTest.xml include $(BUILD_PACKAGE) +# StagedRollbackTest +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under, StagedRollbackTest/src) +LOCAL_MODULE := StagedRollbackTest +LOCAL_MODULE_TAGS := tests +LOCAL_JAVA_LIBRARIES := tradefed +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_TEST_CONFIG := StagedRollbackTest.xml +include $(BUILD_HOST_JAVA_LIBRARY) + # Clean up local variables ROLLBACK_TEST_APP_AV1 := ROLLBACK_TEST_APP_AV2 := diff --git a/tests/RollbackTest/README.txt b/tests/RollbackTest/README.txt new file mode 100644 index 0000000000000..c0b718a3e2c1a --- /dev/null +++ b/tests/RollbackTest/README.txt @@ -0,0 +1,23 @@ +This directory contains a test for the rollback manager service. + +Directory structure +=================== +RollbackTest + - device driven test for rollbacks not involving staged rollbacks. + +StagedRollbackTest + - device driven test for staged rollbacks. + +TestApp + - source for dummy apks used in testing. + +TestApex + - source for dummy apex modules used in testing. + +Running the tests +================= + +You can manually run the tests as follows: + + atest RollbackTest + atest StagedRollbackTest diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index adbad56cd9345..ac39f853656aa 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -21,5 +21,9 @@ diff --git a/tests/RollbackTest/AndroidManifest.xml b/tests/RollbackTest/RollbackTest/AndroidManifest.xml similarity index 100% rename from tests/RollbackTest/AndroidManifest.xml rename to tests/RollbackTest/RollbackTest/AndroidManifest.xml diff --git a/tests/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java similarity index 100% rename from tests/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java rename to tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/LocalIntentSender.java diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java similarity index 100% rename from tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java rename to tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java similarity index 92% rename from tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java rename to tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index f07ae9f65b1b0..f3edf09c5e95f 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -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 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; - } } diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java similarity index 59% rename from tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java rename to tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index 60c7a59d94561..280ee1d1c5760 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -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. + *

+ * 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 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 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); + } + } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java new file mode 100644 index 0000000000000..297bf869278fd --- /dev/null +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -0,0 +1,131 @@ +/* + * 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. + *

+ * 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()); + } +} diff --git a/tests/RollbackTest/StagedRollbackTest.xml b/tests/RollbackTest/StagedRollbackTest.xml new file mode 100644 index 0000000000000..2750d3765c20b --- /dev/null +++ b/tests/RollbackTest/StagedRollbackTest.xml @@ -0,0 +1,25 @@ + + + + diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java new file mode 100644 index 0000000000000..6cb0dd091392e --- /dev/null +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -0,0 +1,56 @@ +/* + * 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.host; + +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Runs the staged rollback tests. + */ +@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. + *

+ * For example, runPhase("testBasicEnableRollback"); + */ + 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 testBasic() throws Exception { + runPhase("testBasicEnableRollback"); + getDevice().reboot(); + runPhase("testBasicCommitRollback"); + getDevice().reboot(); + runPhase("testBasicConfirmRollback"); + } +}