1 /* 2 * Copyright (C) 2020 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.apex; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.junit.Assume.assumeTrue; 23 24 import android.cts.install.lib.host.InstallUtilsHost; 25 26 import com.android.apex.ApexInfo; 27 import com.android.apex.XmlParser; 28 import com.android.tests.rollback.host.AbandonSessionsRule; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 32 33 import org.junit.After; 34 import org.junit.Before; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.time.Duration; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 /** 47 * Host side integration tests for apexd. 48 */ 49 @RunWith(DeviceJUnit4ClassRunner.class) 50 public class ApexdHostTest extends BaseHostJUnit4Test { 51 52 private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex"; 53 54 private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); 55 56 @Rule 57 public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); 58 59 private boolean mWasAdbRoot = false; 60 61 @Before setUp()62 public void setUp() throws Exception { 63 mHostUtils.uninstallShimApexIfNecessary(); 64 mWasAdbRoot = getDevice().isAdbRoot(); 65 if (!mWasAdbRoot) { 66 assumeTrue("Device requires root", getDevice().enableAdbRoot()); 67 } 68 } 69 70 @After tearDown()71 public void tearDown() throws Exception { 72 mHostUtils.uninstallShimApexIfNecessary(); 73 if (!mWasAdbRoot) { 74 getDevice().disableAdbRoot(); 75 } 76 } 77 78 @Test testOrphanedApexIsNotActivated()79 public void testOrphanedApexIsNotActivated() throws Exception { 80 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 81 assumeTrue("Device requires root", getDevice().isAdbRoot()); 82 try { 83 assertThat(getDevice().pushFile(mHostUtils.getTestFile("apex.apexd_test_v2.apex"), 84 "/data/apex/active/apexd_test_v2.apex")).isTrue(); 85 getDevice().reboot(); 86 assertWithMessage("Timed out waiting for device to boot").that( 87 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 88 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 89 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 90 "com.android.apex.test_package", 2L); 91 assertThat(activeApexes).doesNotContain(testApex); 92 mHostUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex", 93 Duration.ofMinutes(3)); 94 } finally { 95 getDevice().executeShellV2Command("rm /data/apex/active/apexd_test_v2.apex"); 96 } 97 } 98 @Test testApexWithoutPbIsNotActivated()99 public void testApexWithoutPbIsNotActivated() throws Exception { 100 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 101 assumeTrue("Device requires root", getDevice().isAdbRoot()); 102 final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex"; 103 try { 104 assertThat(getDevice().pushFile(mHostUtils.getTestFile(testApexFile), 105 "/data/apex/active/" + testApexFile)).isTrue(); 106 getDevice().reboot(); 107 assertWithMessage("Timed out waiting for device to boot").that( 108 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 109 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 110 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 111 "com.android.apex.cts.shim", 2L); 112 assertThat(activeApexes).doesNotContain(testApex); 113 mHostUtils.waitForFileDeleted("/data/apex/active/" + testApexFile, 114 Duration.ofMinutes(3)); 115 } finally { 116 getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile); 117 } 118 } 119 120 @Test testRemountApex()121 public void testRemountApex() throws Exception { 122 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 123 assumeTrue("Device requires root", getDevice().isAdbRoot()); 124 final File oldFile = getDevice().pullFile(SHIM_APEX_PATH); 125 try { 126 getDevice().remountSystemWritable(); 127 // In case remount requires a reboot, wait for boot to complete. 128 getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis()); 129 final File newFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex"); 130 // Stop framework 131 getDevice().executeShellV2Command("stop"); 132 // Push new shim APEX. This simulates adb sync. 133 getDevice().pushFile(newFile, SHIM_APEX_PATH); 134 // Ask apexd to remount packages 135 getDevice().executeShellV2Command("cmd -w apexservice remountPackages"); 136 // Start framework 137 getDevice().executeShellV2Command("start"); 138 // Give enough time for system_server to boot. 139 Thread.sleep(Duration.ofSeconds(15).toMillis()); 140 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 141 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 142 "com.android.apex.cts.shim", 2L); 143 assertThat(activeApexes).contains(testApex); 144 } finally { 145 getDevice().pushFile(oldFile, SHIM_APEX_PATH); 146 getDevice().reboot(); 147 } 148 } 149 150 @Test testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion()151 public void testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion() 152 throws Exception { 153 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 154 assumeTrue("Device requires root", getDevice().isAdbRoot()); 155 156 try { 157 getDevice().remountSystemWritable(); 158 // In case remount requires a reboot, wait for boot to complete. 159 assertWithMessage("Timed out waiting for device to boot").that( 160 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 161 162 final File v1 = mHostUtils.getTestFile("apex.apexd_test.apex"); 163 getDevice().pushFile(v1, "/product/apex/apex.apexd_test.apex"); 164 165 final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex"); 166 getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex"); 167 168 getDevice().reboot(); 169 assertWithMessage("Timed out waiting for device to boot").that( 170 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 171 172 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 173 assertThat(activeApexes).contains(new ITestDevice.ApexInfo( 174 "com.android.apex.test_package", 1L)); 175 assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo( 176 "com.android.apex.test_package", 2L)); 177 178 // v2_no_pb should be deleted 179 mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex", 180 Duration.ofMinutes(3)); 181 } finally { 182 getDevice().remountSystemWritable(); 183 assertWithMessage("Timed out waiting for device to boot").that( 184 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 185 186 getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test.apex"); 187 getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex"); 188 } 189 } 190 191 @Test testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion()192 public void testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion() 193 throws Exception { 194 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 195 assumeTrue("Device requires root", getDevice().isAdbRoot()); 196 197 try { 198 getDevice().remountSystemWritable(); 199 // In case remount requires a reboot, wait for boot to complete. 200 assertWithMessage("Timed out waiting for device to boot").that( 201 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 202 203 final File v3 = mHostUtils.getTestFile("apex.apexd_test_v3.apex"); 204 getDevice().pushFile(v3, "/product/apex/apex.apexd_test_v3.apex"); 205 206 final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex"); 207 getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex"); 208 209 getDevice().reboot(); 210 assertWithMessage("Timed out waiting for device to boot").that( 211 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 212 213 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 214 assertThat(activeApexes).contains(new ITestDevice.ApexInfo( 215 "com.android.apex.test_package", 3L)); 216 assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo( 217 "com.android.apex.test_package", 2L)); 218 219 // v2_no_pb should be deleted 220 mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex", 221 Duration.ofMinutes(3)); 222 } finally { 223 getDevice().remountSystemWritable(); 224 assertWithMessage("Timed out waiting for device to boot").that( 225 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 226 227 getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test_v3.apex"); 228 getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex"); 229 } 230 } 231 232 @Test testApexInfoListIsValid()233 public void testApexInfoListIsValid() throws Exception { 234 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 235 assumeTrue("Device requires root", getDevice().isAdbRoot()); 236 237 try (FileInputStream fis = new FileInputStream( 238 getDevice().pullFile("/apex/apex-info-list.xml"))) { 239 // #1 Data got from apexd via binder 240 Set<ITestDevice.ApexInfo> fromApexd = getDevice().getActiveApexes(); 241 // #2 Data got from the xml file (key is the path) 242 Map<String, ApexInfo> fromXml = XmlParser.readApexInfoList(fis).getApexInfo().stream() 243 .collect(Collectors.toMap(ApexInfo::getModulePath, ai -> ai)); 244 245 // Make sure that all items in #1 are also in #2 and they are identical 246 for (ITestDevice.ApexInfo ai : fromApexd) { 247 ApexInfo apexFromXml = fromXml.get(ai.sourceDir); 248 assertWithMessage("APEX (" + ai.toString() + ") is not found in the list") 249 .that(apexFromXml).isNotNull(); 250 assertWithMessage("Version mismatch for APEX (" + ai.toString() + ")") 251 .that(ai.versionCode).isEqualTo(apexFromXml.getVersionCode()); 252 assertWithMessage("APEX (" + ai.toString() + ") is not active") 253 .that(apexFromXml.getIsActive()).isTrue(); 254 } 255 } 256 } 257 258 /** 259 * Test to verify that the state of a staged session does not change if apexd is stopped and 260 * restarted while a session is staged. 261 */ 262 @Test testApexSessionStateUnchangedBeforeReboot()263 public void testApexSessionStateUnchangedBeforeReboot() throws Exception { 264 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 265 assumeTrue("Device requires root", getDevice().isAdbRoot()); 266 267 File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex"); 268 String error = mHostUtils.installStagedPackage(apexFile); 269 assertThat(error).isNull(); 270 String sessionId = getDevice().executeShellCommand( 271 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim(); 272 assertThat(sessionId).isNotEmpty(); 273 String sessionStateCmd = "cmd -w apexservice getStagedSessionInfo " + sessionId; 274 String initialState = getDevice().executeShellV2Command(sessionStateCmd).getStdout(); 275 assertThat(initialState).isNotEmpty(); 276 277 // Kill apexd. This means apexd will perform its start logic when the second install 278 // is staged. 279 getDevice().executeShellV2Command("kill `pidof apexd`"); 280 281 // Verify that the session state remains consistent after apexd has restarted. 282 String updatedState = getDevice().executeShellV2Command(sessionStateCmd).getStdout(); 283 assertThat(updatedState).isEqualTo(initialState); 284 } 285 286 /** 287 * Verifies that content of {@code /data/apex/sessions/} is migrated to the {@code 288 * /metadata/apex/sessions}. 289 */ 290 @Test testSessionsDirMigrationToMetadata()291 public void testSessionsDirMigrationToMetadata() throws Exception { 292 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 293 assumeTrue("Device requires root", getDevice().isAdbRoot()); 294 295 try { 296 getDevice().executeShellV2Command("mkdir -p /data/apex/sessions/1543"); 297 File file = File.createTempFile("foo", "bar"); 298 getDevice().pushFile(file, "/data/apex/sessions/1543/file"); 299 300 // During boot sequence apexd will move /data/apex/sessions/1543/file to 301 // /metadata/apex/sessions/1543/file. 302 getDevice().reboot(); 303 assertWithMessage("Timed out waiting for device to boot").that( 304 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 305 306 assertThat(getDevice().doesFileExist("/metadata/apex/sessions/1543/file")).isTrue(); 307 assertThat(getDevice().doesFileExist("/data/apex/sessions/1543/file")).isFalse(); 308 } finally { 309 getDevice().executeShellV2Command("rm -R /data/apex/sessions/1543"); 310 getDevice().executeShellV2Command("rm -R /metadata/apex/sessions/1543"); 311 } 312 } 313 314 @Test testFailsToActivateApexOnDataFallbacksToPreInstalled()315 public void testFailsToActivateApexOnDataFallbacksToPreInstalled() throws Exception { 316 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 317 assumeTrue("Device requires root", getDevice().isAdbRoot()); 318 319 try { 320 final File file = 321 mHostUtils.getTestFile("com.android.apex.cts.shim.v2_additional_file.apex"); 322 getDevice().pushFile(file, "/data/apex/active/com.android.apex.cts.shim@2.apex"); 323 324 getDevice().reboot(); 325 assertWithMessage("Timed out waiting for device to boot").that( 326 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 327 328 // After reboot pre-installed version of shim apex should be activated, and corrupted 329 // version on /data should be deleted. 330 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 331 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 332 "com.android.apex.cts.shim", 1L); 333 assertThat(activeApexes).contains(testApex); 334 assertThat( 335 getDevice() 336 .doesFileExist("/data/apex/active/com.android.apex.cts.shim@2.apex")) 337 .isFalse(); 338 } finally { 339 getDevice().deleteFile("/data/apex/active/com.android.apex.cts.shim@2.apex"); 340 } 341 } 342 } 343