blob: b51aad1d63f82da0557fb12ca577f321f549cdf5 [file] [log] [blame]
/*
* 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.cts.rollback.lib.RollbackInfoSubject.assertThat;
import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.Manifest;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.ParcelFileDescriptor;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
import com.android.cts.install.lib.Install;
import com.android.cts.install.lib.InstallUtils;
import com.android.cts.install.lib.LocalIntentSender;
import com.android.cts.install.lib.TestApp;
import com.android.cts.install.lib.Uninstall;
import com.android.cts.rollback.lib.Rollback;
import com.android.cts.rollback.lib.RollbackUtils;
import com.android.internal.R;
import libcore.io.IoUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.concurrent.TimeUnit;
/**
* 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 NETWORK_STACK_CONNECTOR_CLASS =
"android.net.INetworkStackConnector";
private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
"watchdog_trigger_failure_count";
private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
/**
* Adopts common shell permissions needed for rollback tests.
*/
@Before
public void adoptShellPermissions() {
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.FORCE_STOP_PACKAGES,
Manifest.permission.WRITE_DEVICE_CONFIG);
}
/**
* Drops shell permissions needed for rollback tests.
*/
@After
public void dropShellPermissions() {
InstallUtils.dropShellPermissionIdentity();
}
/**
* Test rollbacks of staged installs involving only apks with bad update.
* Enable rollback phase.
*/
@Test
public void testBadApkOnly_Phase1() throws Exception {
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
}
/**
* Test rollbacks of staged installs involving only apks with bad update.
* Confirm that rollback was successfully enabled.
*/
@Test
public void testBadApkOnly_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
RollbackManager rm = RollbackUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TestApp.A);
assertThat(rollback).isNotNull();
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(rollback.isStaged()).isTrue();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(5), false);
RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
// Sleep for a while to make sure we don't trigger rollback
Thread.sleep(TimeUnit.SECONDS.toMillis(30));
}
/**
* Test rollbacks of staged installs involving only apks with bad update.
* Trigger rollback phase. This is expected to fail due to watchdog
* rebooting the test out from under it.
*/
@Test
public void testBadApkOnly_Phase3() throws Exception {
// One more crash to trigger rollback
RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
// We expect the device to be rebooted automatically. Wait for that to happen.
Thread.sleep(30 * 1000);
// Raise an error anyway if reboot didn't happen.
fail("watchdog did not trigger reboot");
}
/**
* Test rollbacks of staged installs involving only apks.
* Confirm rollback phase.
*/
@Test
public void testBadApkOnly_Phase4() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
RollbackManager rm = RollbackUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TestApp.A);
assertThat(rollback).isNotNull();
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
assertThat(rollback).isStaged();
assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
}
/**
* Stage install ModuleMetadata package to simulate a Mainline module update.
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
resetModuleMetadataPackage();
Context context = InstrumentationRegistry.getContext();
PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
MODULE_META_DATA_PACKAGE, 0);
String metadataApkPath = metadataPackageInfo.applicationInfo.sourceDir;
assertThat(metadataApkPath).isNotNull();
assertThat(metadataApkPath).isNotEqualTo("");
runShellCommand("pm install "
+ "-r --enable-rollback --staged --wait "
+ metadataApkPath);
}
/**
* Verify the rollback is available.
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase2() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
MODULE_META_DATA_PACKAGE)).isNotNull();
}
/**
* Verify the rollback is committed after crashing.
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase3() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
MODULE_META_DATA_PACKAGE)).isNotNull();
}
@Test
public void testNetworkFailedRollback_Phase1() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
String networkStack = getNetworkStackPackageName();
rm.expireRollbackForPackage(networkStack);
uninstallNetworkStackPackage();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
networkStack)).isNull();
}
@Test
public void testNetworkFailedRollback_Phase2() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
getNetworkStackPackageName())).isNotNull();
}
@Test
public void testNetworkFailedRollback_Phase3() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNull();
}
@Test
public void testNetworkFailedRollback_Phase4() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNotNull();
}
private String getNetworkStackPackageName() {
Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
ComponentName comp = intent.resolveSystemService(
InstrumentationRegistry.getContext().getPackageManager(), 0);
return comp.getPackageName();
}
private void uninstallNetworkStackPackage() {
// Since the host side use shell command to install the network stack package, uninstall
// must be done by shell command as well. Otherwise uninstall by a different user will fail.
runShellCommand("pm uninstall " + getNetworkStackPackageName());
}
@Test
public void testPreviouslyAbandonedRollbacks_Phase1() throws Exception {
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
int sessionId = Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
PackageInstaller pi = InstrumentationRegistry.getContext().getPackageManager()
.getPackageInstaller();
pi.abandonSession(sessionId);
// Remove the first intent sender result, so that the next staged install session does not
// erroneously think that it has itself been abandoned.
// TODO(b/136260017): Restructure LocalIntentSender to negate the need for this step.
LocalIntentSender.getIntentSenderResult();
Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
}
@Test
public void testPreviouslyAbandonedRollbacks_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
RollbackManager rm = RollbackUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TestApp.A);
RollbackUtils.rollback(rollback.getRollbackId());
}
@Test
public void testPreviouslyAbandonedRollbacks_Phase3() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
Uninstall.packages(TestApp.A);
}
@Test
public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
String networkStack = getNetworkStackPackageName();
rm.expireRollbackForPackage(networkStack);
uninstallNetworkStackPackage();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
networkStack)).isNull();
}
@Test
public void testNetworkPassedDoesNotRollback_Phase2() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
getNetworkStackPackageName())).isNotNull();
}
@Test
public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNull();
}
@Nullable
private static String getModuleMetadataPackageName() {
String packageName = InstrumentationRegistry.getContext().getResources().getString(
R.string.config_defaultModuleMetadataProvider);
if (TextUtils.isEmpty(packageName)) {
return null;
}
return packageName;
}
private void resetModuleMetadataPackage() {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(MODULE_META_DATA_PACKAGE).isNotNull();
rm.expireRollbackForPackage(MODULE_META_DATA_PACKAGE);
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
MODULE_META_DATA_PACKAGE)).isNull();
}
private void runShellCommand(String cmd) {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
IoUtils.closeQuietly(pfd);
}
}