blob: 05c4ca772fa466170750496bedf97f027ce492fa [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.cts.rollback;
import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
import static com.android.cts.rollback.lib.RollbackUtils.getRollbackManager;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.provider.DeviceConfig;
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.RollbackBroadcastReceiver;
import com.android.cts.rollback.lib.RollbackUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* CTS Tests for RollbackManager APIs.
*/
@RunWith(JUnit4.class)
public class RollbackManagerTest {
// TODO: use PackageManager.RollbackDataPolicy.* when they are system API
private static final int ROLLBACK_DATA_POLICY_RESTORE = 0;
private static final int ROLLBACK_DATA_POLICY_WIPE = 1;
private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout";
/**
* Adopts common permissions needed to test rollbacks and uninstalls the
* test apps.
*/
@Before
public void setup() throws InterruptedException, IOException {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.FORCE_STOP_PACKAGES,
Manifest.permission.SET_TIME);
Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
}
/**
* Drops adopted shell permissions and uninstalls the test apps.
*/
@After
public void teardown() throws InterruptedException, IOException {
Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
/**
* Test basic rollbacks.
*/
@Test
public void testBasic() throws Exception {
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
RollbackUtils.waitForRollbackGone(
() -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
RollbackUtils.waitForRollbackGone(
() -> getRollbackManager().getRecentlyCommittedRollbacks(), TestApp.A);
Install.single(TestApp.A2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(available).isNotStaged();
assertThat(available).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
RollbackUtils.waitForUnavailableRollback(TestApp.A);
RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
assertThat(committed).isNotNull();
assertThat(committed).hasRollbackId(available.getRollbackId());
assertThat(committed).isNotStaged();
assertThat(committed).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(committed).causePackagesContainsExactly(TestApp.A2);
}
/**
* Tests rollback of multi-package installs is implemented.
*/
@Test
public void testBasic_MultiPackage() throws Exception {
// Prep installation of the test apps.
Install.multi(TestApp.A1, TestApp.B1).commit();
Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// TestApp.A should now be available for rollback.
RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(rollback).isNotNull();
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1),
Rollback.from(TestApp.B2).to(TestApp.B1));
// Rollback the app. It should cause both test apps to be rolled back.
RollbackUtils.rollback(rollback.getRollbackId());
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
// We should see recent rollbacks listed for both A and B.
RollbackInfo rollbackA = RollbackUtils.getCommittedRollback(TestApp.A);
RollbackInfo rollbackB = RollbackUtils.getCommittedRollback(TestApp.B);
assertThat(rollbackA).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1),
Rollback.from(TestApp.B2).to(TestApp.B1));
assertThat(rollbackA).hasRollbackId(rollback.getRollbackId());
assertThat(rollbackB).hasRollbackId(rollback.getRollbackId());
}
/**
* Tests rollbacks are properly persisted.
*/
@Test
public void testSingleRollbackPersistence() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(rollbackA).isNotNull();
// Check the available rollback is persisted correctly
rm.reloadPersistedData();
rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
// Rollback the app
TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
/*versionCode*/ 42, /*isApex*/ false);
RollbackUtils.rollback(rollbackA.getRollbackId(), cause);
RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
assertThat(committed).isNotNull();
// Check the committed rollback is persisted correctly
rm.reloadPersistedData();
committed = RollbackUtils.getCommittedRollback(TestApp.A);
assertThat(committed).hasRollbackId(rollbackA.getRollbackId());
assertThat(committed).isNotStaged();
assertThat(committed).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(committed).causePackagesContainsExactly(cause);
}
/**
* Tests rollbacks are properly persisted.
*/
@Test
public void testMultiRollbackPersistence() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
Install.multi(TestApp.A1, TestApp.B1).commit();
Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
assertThat(rollbackA).isNotNull();
assertThat(rollbackB).isNotNull();
// Check the available rollback is persisted correctly
rm.reloadPersistedData();
rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
rollbackB = RollbackUtils.getAvailableRollback(TestApp.B);
assertThat(rollbackB).hasRollbackId(rollbackA.getRollbackId());
assertThat(rollbackA).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1),
Rollback.from(TestApp.B2).to(TestApp.B1));
// Rollback the app
RollbackUtils.rollback(rollbackA.getRollbackId());
RollbackInfo committedA = RollbackUtils.getCommittedRollback(TestApp.A);
RollbackInfo committedB = RollbackUtils.getCommittedRollback(TestApp.B);
assertThat(committedA).isNotNull();
assertThat(committedB).isNotNull();
// Check the committed rollback is persisted correctly
rm.reloadPersistedData();
committedA = RollbackUtils.getCommittedRollback(TestApp.A);
committedB = RollbackUtils.getCommittedRollback(TestApp.B);
assertThat(committedA).hasRollbackId(rollbackA.getRollbackId());
assertThat(committedB).hasRollbackId(rollbackA.getRollbackId());
assertThat(committedA).isNotStaged();
assertThat(committedA).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1),
Rollback.from(TestApp.B2).to(TestApp.B1));
}
/**
* Tests that the MANAGE_ROLLBACKS permission is required to call
* RollbackManager APIs.
*/
@Test
public void testManageRollbacksPermission() throws Exception {
// We shouldn't be allowed to call any of the RollbackManager APIs
// without the MANAGE_ROLLBACKS permission.
InstallUtils.dropShellPermissionIdentity();
RollbackManager rm = RollbackUtils.getRollbackManager();
try {
rm.getAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
rm.getRecentlyCommittedRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
LocalIntentSender sender = new LocalIntentSender();
rm.commitRollback(0, Collections.emptyList(), sender.getIntentSender());
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
rm.reloadPersistedData();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
rm.expireRollbackForPackage(TestApp.A);
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
}
/**
* Tests that you cannot enable rollback for a package without the
* MANAGE_ROLLBACKS permission.
*/
@Test
public void testEnableRollbackPermission() throws Exception {
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES);
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
// We expect v2 of the app was installed, but rollback has not
// been enabled.
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.MANAGE_ROLLBACKS,
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES);
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
}
/**
* Tests that you cannot enable rollback for a non-module package when
* holding the MANAGE_ROLLBACKS permission without TEST_MANAGE_ROLLBACKS.
*/
@Test
public void testNonModuleEnableRollback() throws Exception {
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
// We expect v2 of the app was installed, but rollback has not
// been enabled because the test app is not a module.
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
}
/**
* Tests failure to enable rollback for multi-package installs.
* If any one of the packages fail to enable rollback, we shouldn't enable
* rollback for any package.
*/
@Test
public void testMultiPackageEnableFail() throws Exception {
Install.single(TestApp.A1).commit();
// We should fail to enable rollback here because TestApp B is not
// already installed.
Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
}
@Test
public void testGetRollbackDataPolicy() throws Exception {
final int rollBackDataPolicy = ROLLBACK_DATA_POLICY_WIPE;
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Enable rollback with rollBackDataPolicy
final int sessionId = Install.single(TestApp.A2).setEnableRollback(
rollBackDataPolicy).createSession();
try {
assertThat(InstallUtils.getPackageInstaller().getSessionInfo(
sessionId).getRollbackDataPolicy()).isEqualTo(rollBackDataPolicy);
} finally {
// Abandon the session
InstallUtils.getPackageInstaller().abandonSession(sessionId);
}
}
/**
* Test that flags are cleared when a rollback is committed.
*/
@Test
public void testRollbackClearsFlags() throws Exception {
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
RollbackUtils.waitForRollbackGone(
() -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
Install.single(TestApp.A2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
DeviceConfig.setProperty("configuration", "namespace_to_package_mapping",
"testspace:" + TestApp.A, false);
DeviceConfig.setProperty("testspace", "flagname", "hello", false);
DeviceConfig.setProperty("testspace", "another", "12345", false);
assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(2);
RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(0);
}
/**
* Tests an app can be rolled back to the previous signing key.
*
* <p>The rollback capability in the signing lineage allows an app to be updated to an APK
* signed with a previous signing key in the lineage; however this often defeats the purpose
* of key rotation as a compromised key could then be used to roll an app back to the previous
* key. To avoid requiring the rollback capability to support APK rollbacks the PackageManager
* allows an app to be rolled back to the previous signing key if the rollback install reason
* is set.
*/
@Test
public void testRollbackAfterKeyRotation() throws Exception {
Install.single(TestApp.AOriginal1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
Install.single(TestApp.ARotated2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
RollbackUtils.rollback(available.getRollbackId(), TestApp.ARotated2);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
}
/**
* Test we can't enable rollback for non-allowlisted app without
* TEST_MANAGE_ROLLBACKS permission
*/
@Test
public void testNonRollbackAllowlistedApp() throws Exception {
InstallUtils.dropShellPermissionIdentity();
InstallUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
Install.single(TestApp.A1).commit();
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
Install.single(TestApp.A2).setEnableRollback().commit();
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
}
/**
* Tests user data is restored according to the preset rollback data policy.
*/
@Test
public void testRollbackDataPolicy() throws Exception {
Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
// Write user data version = 1
InstallUtils.processUserData(TestApp.A);
InstallUtils.processUserData(TestApp.B);
InstallUtils.processUserData(TestApp.C);
Install a2 = Install.single(TestApp.A2)
.setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_WIPE);
Install b2 = Install.single(TestApp.B2)
.setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
Install c2 = Install.single(TestApp.C2)
.setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN);
Install.multi(a2, b2, c2).setEnableRollback().commit();
// Write user data version = 2
InstallUtils.processUserData(TestApp.A);
InstallUtils.processUserData(TestApp.B);
InstallUtils.processUserData(TestApp.C);
RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
RollbackUtils.rollback(info.getRollbackId());
// Read user data version from userdata.txt
// A's user data version is -1 for user data is wiped.
// B's user data version is 1 for user data is restored.
// C's user data version is 2 for user data is retained.
assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
}
/**
* Tests user data is restored according to the rollback data policy defined in the manifest.
*/
@Test
public void testRollbackDataPolicy_Manifest() throws Exception {
Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
// Write user data version = 1
InstallUtils.processUserData(TestApp.A);
InstallUtils.processUserData(TestApp.B);
InstallUtils.processUserData(TestApp.C);
Install a2 = Install.single(TestApp.ARollbackWipe2).setEnableRollback();
Install b2 = Install.single(TestApp.BRollbackRestore2).setEnableRollback();
Install c2 = Install.single(TestApp.CRollbackRetain2).setEnableRollback();
Install.multi(a2, b2, c2).setEnableRollback().commit();
// Write user data version = 2
InstallUtils.processUserData(TestApp.A);
InstallUtils.processUserData(TestApp.B);
InstallUtils.processUserData(TestApp.C);
RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
RollbackUtils.rollback(info.getRollbackId());
// Read user data version from userdata.txt
// A's user data version is -1 for user data is wiped.
// B's user data version is 1 for user data is restored.
// C's user data version is 2 for user data is retained.
assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
}
/**
* Test explicit expiration of rollbacks.
* Does not test the scheduling aspects of rollback expiration.
*/
@Test
public void testRollbackExpiration() throws Exception {
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
RollbackUtils.waitForAvailableRollback(TestApp.A);
// Expire the rollback.
RollbackUtils.getRollbackManager().expireRollbackForPackage(TestApp.A);
// The rollback should no longer be available.
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
}
/**
* Test restrictions on rollback broadcast sender.
* A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
*/
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
} catch (SecurityException se) {
// Expected behavior.
}
// Confirm that we really haven't received the broadcast.
// TODO: How long to wait for the expected timeout?
assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull();
// TODO: Do we need to do this? Do we need to ensure this is always
// called, even when the test fails?
broadcastReceiver.unregister();
}
/**
* Test rollback of apks involving splits.
*/
@Test
public void testRollbackWithSplits() throws Exception {
Install.single(TestApp.ASplit1).commit();
Install.single(TestApp.ASplit2).setEnableRollback().commit();
RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
RollbackUtils.rollback(rollback.getRollbackId());
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
}
/**
* Test bad update automatic rollback.
*/
@Test
public void testBadUpdateRollback() throws Exception {
// Prep installation of the test apps.
Install.single(TestApp.A1).commit();
Install.single(TestApp.ACrashing2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
Install.single(TestApp.B1).commit();
Install.single(TestApp.B2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// Both test apps should now be available for rollback, and the
// targetPackage returned for rollback should be correct.
RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
assertThat(rollbackB).packagesContainsExactly(Rollback.from(TestApp.B2).to(TestApp.B1));
// Register rollback committed receiver
RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();
// Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
// Verify we received a broadcast for the rollback.
assertThat(rollbackReceiver.poll(1, TimeUnit.MINUTES)).isNotNull();
// TestApp.A is automatically rolled back by the RollbackPackageHealthObserver
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Instrumented app is still the package installer
String installer = InstrumentationRegistry.getContext().getPackageManager()
.getInstallerPackageName(TestApp.A);
assertThat(installer).isEqualTo(getClass().getPackageName());
// TestApp.B is untouched
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
}
/**
* Tests we fail to enable rollbacks if enable-rollback times out.
*/
@Test
public void testEnableRollbackTimeoutFailsRollback() throws Exception {
//setting the timeout to a very short amount that will definitely be triggered
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
Long.toString(0), false /* makeDefault*/);
try {
Install.single(TestApp.A1).commit();
RollbackUtils.waitForUnavailableRollback(TestApp.A);
// Block the RollbackManager to make extra sure it will not be
// able to enable the rollback in time.
RollbackManager rm = RollbackUtils.getRollbackManager();
rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1));
Install.single(TestApp.A2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// Give plenty of time for RollbackManager to unblock and attempt
// to make the rollback available before asserting that the
// rollback was not made available.
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
} finally {
//setting the timeout back to default
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
null, false /* makeDefault*/);
}
}
/**
* Tests we fail to enable rollbacks if enable-rollback times out for any child session.
*/
@Test
public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
Long.toString(5000), false /* makeDefault*/);
try {
Install.multi(TestApp.A1, TestApp.B1).commit();
RollbackUtils.waitForUnavailableRollback(TestApp.A);
// Block the 2nd session for 10s so it will not be able to enable the rollback in time.
RollbackManager rm = RollbackUtils.getRollbackManager();
rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0));
rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10));
Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
// Give plenty of time for RollbackManager to unblock and attempt
// to make the rollback available before asserting that the
// rollback was not made available.
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
} finally {
//setting the timeout back to default
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
null, false /* makeDefault*/);
}
}
/**
* Test the scheduling aspect of rollback expiration.
*/
@Test
public void testRollbackExpiresAfterLifetime() throws Exception {
long expirationTime = TimeUnit.SECONDS.toMillis(30);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
try {
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
RollbackUtils.waitForAvailableRollback(TestApp.A);
// Give it a little more time, but still not long enough to expire
Thread.sleep(expirationTime / 2);
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
// Check that the data has expired after the expiration time (with a buffer of 1 second)
Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
} finally {
// Restore default config values
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
null, false /* makeDefault*/);
}
}
/**
* Test that available rollbacks should expire correctly when the property
* {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed
*/
@Test
public void testRollbackExpiresWhenLifetimeChanges() throws Exception {
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
RollbackUtils.waitForAvailableRollback(TestApp.A);
// Change the lifetime to 0 which should expire rollbacks immediately
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(0), false /* makeDefault*/);
try {
RollbackUtils.waitForUnavailableRollback(TestApp.A);
} finally {
// Restore default config values
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
null, false /* makeDefault*/);
}
}
/**
* Test that changing time on device does not affect the duration of time that we keep
* rollback available
*/
@Test
public void testTimeChangeDoesNotAffectLifetime() throws Exception {
long expirationTime = TimeUnit.SECONDS.toMillis(30);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
try {
Install.single(TestApp.A1).commit();
Install.single(TestApp.A2).setEnableRollback().commit();
RollbackUtils.waitForAvailableRollback(TestApp.A);
RollbackUtils.forwardTimeBy(expirationTime);
try {
// The rollback should be still available after forwarding time
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
// The rollback shouldn't expire when half of the expiration time elapses
Thread.sleep(expirationTime / 2);
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
// The rollback now should expire
Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
} finally {
RollbackUtils.forwardTimeBy(-expirationTime);
}
} finally {
// Restore default config values
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
null, false /* makeDefault*/);
}
}
}