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
This commit is contained in:
Richard Uhler
2019-02-11 12:20:31 +00:00
parent 00baa4b4fc
commit c5e2e803fd
5 changed files with 314 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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