1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tests.rollback.host; 18 19 import static com.android.tests.rollback.host.WatchdogEventLogger.Subject.assertThat; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.junit.Assume.assumeTrue; 25 26 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 28 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 29 import com.android.tradefed.util.CommandResult; 30 import com.android.tradefed.util.CommandStatus; 31 32 import org.junit.After; 33 import org.junit.Before; 34 import org.junit.Rule; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 import java.io.File; 39 import java.util.concurrent.TimeUnit; 40 41 /** 42 * Runs the staged rollback tests. 43 * 44 * TODO(gavincorkery): Support the verification of logging parents in Watchdog metrics. 45 */ 46 @RunWith(DeviceJUnit4ClassRunner.class) 47 public class StagedRollbackTest extends BaseHostJUnit4Test { 48 private static final String TAG = "StagedRollbackTest"; 49 private static final int NATIVE_CRASHES_THRESHOLD = 5; 50 51 /** 52 * Runs the given phase of a test by calling into the device. 53 * Throws an exception if the test phase fails. 54 * <p> 55 * For example, <code>runPhase("testApkOnlyEnableRollback");</code> 56 */ runPhase(String phase)57 private void runPhase(String phase) throws Exception { 58 assertThat(runDeviceTests("com.android.tests.rollback", 59 "com.android.tests.rollback.StagedRollbackTest", 60 phase)).isTrue(); 61 } 62 63 private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; 64 private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; 65 66 private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; 67 private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; 68 69 private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; 70 private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; 71 private static final String ROLLBACK_SUCCESS = "ROLLBACK_SUCCESS"; 72 73 private WatchdogEventLogger mLogger = new WatchdogEventLogger(); 74 75 @Rule 76 public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); 77 78 @Before setUp()79 public void setUp() throws Exception { 80 deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", 81 "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); 82 runPhase("expireRollbacks"); 83 mLogger.start(getDevice()); 84 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); 85 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.B"); 86 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.C"); 87 } 88 89 @After tearDown()90 public void tearDown() throws Exception { 91 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); 92 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.B"); 93 getDevice().uninstallPackage("com.android.cts.install.lib.testapp.C"); 94 mLogger.stop(); 95 runPhase("expireRollbacks"); 96 deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", 97 "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", 98 apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*", 99 apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*", 100 "/system/apex/test.rebootless_apex_v*.apex", 101 "/data/apex/active/test.apex.rebootless*.apex"); 102 } 103 104 /** 105 * Deletes files and reboots the device if necessary. 106 * @param files the paths of files which might contain wildcards 107 */ deleteFiles(String... files)108 private void deleteFiles(String... files) throws Exception { 109 try { 110 getDevice().enableAdbRoot(); 111 boolean found = false; 112 for (String file : files) { 113 CommandResult result = getDevice().executeShellV2Command("ls " + file); 114 if (result.getStatus() == CommandStatus.SUCCESS) { 115 found = true; 116 break; 117 } 118 } 119 120 if (found) { 121 getDevice().remountSystemWritable(); 122 for (String file : files) { 123 getDevice().executeShellCommand("rm -rf " + file); 124 } 125 getDevice().reboot(); 126 } 127 } finally { 128 getDevice().disableAdbRoot(); 129 } 130 } 131 waitForDeviceNotAvailable(long timeout, TimeUnit unit)132 private void waitForDeviceNotAvailable(long timeout, TimeUnit unit) { 133 assertWithMessage("waitForDeviceNotAvailable() timed out in %s %s", timeout, unit) 134 .that(getDevice().waitForDeviceNotAvailable(unit.toMillis(timeout))).isTrue(); 135 } 136 137 /** 138 * Tests rolling back user data where there are multiple rollbacks for that package. 139 */ 140 @Test testPreviouslyAbandonedRollbacks()141 public void testPreviouslyAbandonedRollbacks() throws Exception { 142 runPhase("testPreviouslyAbandonedRollbacks_Phase1_InstallAndAbandon"); 143 getDevice().reboot(); 144 runPhase("testPreviouslyAbandonedRollbacks_Phase2_Rollback"); 145 getDevice().reboot(); 146 runPhase("testPreviouslyAbandonedRollbacks_Phase3_VerifyRollback"); 147 } 148 149 /** 150 * Tests we can enable rollback for a allowlisted app. 151 */ 152 @Test testRollbackAllowlistedApp()153 public void testRollbackAllowlistedApp() throws Exception { 154 assumeTrue(hasMainlineModule()); 155 runPhase("testRollbackAllowlistedApp_Phase1_Install"); 156 getDevice().reboot(); 157 runPhase("testRollbackAllowlistedApp_Phase2_VerifyInstall"); 158 } 159 160 /** 161 * Tests that RollbackPackageHealthObserver is observing apk-in-apex. 162 */ 163 @Test testRollbackApexWithApkCrashing()164 public void testRollbackApexWithApkCrashing() throws Exception { 165 pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); 166 167 // Install an apex with apk that crashes 168 runPhase("testRollbackApexWithApkCrashing_Phase1_Install"); 169 getDevice().reboot(); 170 // Verify apex was installed and then crash the apk 171 runPhase("testRollbackApexWithApkCrashing_Phase2_Crash"); 172 // Launch the app to crash to trigger rollback 173 startActivity(TESTAPP_A); 174 // Wait for reboot to happen 175 waitForDeviceNotAvailable(2, TimeUnit.MINUTES); 176 getDevice().waitForDeviceAvailable(); 177 // Verify rollback occurred due to crash of apk-in-apex 178 runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback"); 179 180 assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); 181 assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); 182 assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); 183 } 184 185 /** 186 * Tests rollback is supported correctly for rebootless apex 187 */ 188 @Test testRollbackRebootlessApex()189 public void testRollbackRebootlessApex() throws Exception { 190 pushTestApex("test.rebootless_apex_v1.apex"); 191 runPhase("testRollbackRebootlessApex"); 192 } 193 194 /** 195 * Tests only rebootless apex (if any) is rolled back when native crash happens 196 */ 197 @Test testNativeWatchdogTriggersRebootlessApexRollback()198 public void testNativeWatchdogTriggersRebootlessApexRollback() throws Exception { 199 pushTestApex("test.rebootless_apex_v1.apex"); 200 runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install"); 201 crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); 202 getDevice().waitForDeviceAvailable(); 203 runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify"); 204 } 205 206 /** 207 * Tests that packages are monitored across multiple reboots. 208 */ 209 @Test testWatchdogMonitorsAcrossReboots()210 public void testWatchdogMonitorsAcrossReboots() throws Exception { 211 runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install"); 212 213 // The first reboot will make the rollback available. 214 // Information about which packages are monitored will be persisted to a file before the 215 // second reboot, and read from disk after the second reboot. 216 getDevice().reboot(); 217 getDevice().reboot(); 218 219 runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall"); 220 221 // Launch the app to crash to trigger rollback 222 startActivity(TESTAPP_A); 223 // Wait for reboot to happen 224 waitForDeviceNotAvailable(2, TimeUnit.MINUTES); 225 getDevice().waitForDeviceAvailable(); 226 227 runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback"); 228 } 229 pushTestApex(String fileName)230 private void pushTestApex(String fileName) throws Exception { 231 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); 232 final File apex = buildHelper.getTestFile(fileName); 233 try { 234 getDevice().enableAdbRoot(); 235 getDevice().remountSystemWritable(); 236 assertThat(getDevice().pushFile(apex, "/system/apex/" + fileName)).isTrue(); 237 } finally { 238 getDevice().disableAdbRoot(); 239 } 240 getDevice().reboot(); 241 } 242 apexDataDirDeSys(String apexName)243 private static String apexDataDirDeSys(String apexName) { 244 return String.format("/data/misc/apexdata/%s", apexName); 245 } 246 apexDataDirCe(String apexName, int userId)247 private static String apexDataDirCe(String apexName, int userId) { 248 return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName); 249 } 250 startActivity(String packageName)251 private void startActivity(String packageName) throws Exception { 252 String cmd = "am start -S -a android.intent.action.MAIN " 253 + "-c android.intent.category.LAUNCHER " + packageName; 254 getDevice().executeShellCommand(cmd); 255 } 256 257 /** 258 * True if this build has mainline modules installed. 259 */ hasMainlineModule()260 private boolean hasMainlineModule() throws Exception { 261 try { 262 runPhase("hasMainlineModule"); 263 return true; 264 } catch (AssertionError ignore) { 265 return false; 266 } 267 } 268 crashProcess(String processName, int numberOfCrashes)269 private void crashProcess(String processName, int numberOfCrashes) throws Exception { 270 String pid = ""; 271 String lastPid = "invalid"; 272 for (int i = 0; i < numberOfCrashes; ++i) { 273 // This condition makes sure before we kill the process, the process is running AND 274 // the last crash was finished. 275 while ("".equals(pid) || lastPid.equals(pid)) { 276 pid = getDevice().executeShellCommand("pidof " + processName); 277 } 278 getDevice().executeShellCommand("kill " + pid); 279 lastPid = pid; 280 } 281 } 282 } 283