1*4e2b41f1SAndroid Build Coastguard Worker /* 2*4e2b41f1SAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project 3*4e2b41f1SAndroid Build Coastguard Worker * 4*4e2b41f1SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*4e2b41f1SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*4e2b41f1SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*4e2b41f1SAndroid Build Coastguard Worker * 8*4e2b41f1SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*4e2b41f1SAndroid Build Coastguard Worker * 10*4e2b41f1SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*4e2b41f1SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*4e2b41f1SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*4e2b41f1SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*4e2b41f1SAndroid Build Coastguard Worker * limitations under the License. 15*4e2b41f1SAndroid Build Coastguard Worker */ 16*4e2b41f1SAndroid Build Coastguard Worker 17*4e2b41f1SAndroid Build Coastguard Worker package com.android.tests.dsu; 18*4e2b41f1SAndroid Build Coastguard Worker 19*4e2b41f1SAndroid Build Coastguard Worker import static org.junit.Assert.assertEquals; 20*4e2b41f1SAndroid Build Coastguard Worker import static org.junit.Assert.assertNotNull; 21*4e2b41f1SAndroid Build Coastguard Worker import static org.junit.Assert.assertTrue; 22*4e2b41f1SAndroid Build Coastguard Worker 23*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.config.Option; 24*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.device.DeviceNotAvailableException; 25*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.log.LogUtil.CLog; 26*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 27*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.util.FileUtil; 28*4e2b41f1SAndroid Build Coastguard Worker import com.android.tradefed.util.StreamUtil; 29*4e2b41f1SAndroid Build Coastguard Worker 30*4e2b41f1SAndroid Build Coastguard Worker import org.junit.After; 31*4e2b41f1SAndroid Build Coastguard Worker import org.junit.Before; 32*4e2b41f1SAndroid Build Coastguard Worker import org.junit.Test; 33*4e2b41f1SAndroid Build Coastguard Worker import org.junit.runner.RunWith; 34*4e2b41f1SAndroid Build Coastguard Worker 35*4e2b41f1SAndroid Build Coastguard Worker import java.io.BufferedInputStream; 36*4e2b41f1SAndroid Build Coastguard Worker import java.io.BufferedOutputStream; 37*4e2b41f1SAndroid Build Coastguard Worker import java.io.File; 38*4e2b41f1SAndroid Build Coastguard Worker import java.io.FileInputStream; 39*4e2b41f1SAndroid Build Coastguard Worker import java.io.FileOutputStream; 40*4e2b41f1SAndroid Build Coastguard Worker import java.io.IOException; 41*4e2b41f1SAndroid Build Coastguard Worker import java.io.InputStream; 42*4e2b41f1SAndroid Build Coastguard Worker import java.util.zip.ZipEntry; 43*4e2b41f1SAndroid Build Coastguard Worker import java.util.zip.ZipOutputStream; 44*4e2b41f1SAndroid Build Coastguard Worker 45*4e2b41f1SAndroid Build Coastguard Worker @RunWith(DeviceJUnit4ClassRunner.class) 46*4e2b41f1SAndroid Build Coastguard Worker public class DsuGsiIntegrationTest extends DsuTestBase { 47*4e2b41f1SAndroid Build Coastguard Worker private static final long DSU_MAX_WAIT_SEC = 10 * 60; 48*4e2b41f1SAndroid Build Coastguard Worker private static final long DSU_DEFAULT_USERDATA_SIZE = 8L << 30; 49*4e2b41f1SAndroid Build Coastguard Worker 50*4e2b41f1SAndroid Build Coastguard Worker private static final String GSI_IMAGE_NAME = "system.img"; 51*4e2b41f1SAndroid Build Coastguard Worker private static final String DSU_IMAGE_ZIP_PUSH_PATH = "/sdcard/gsi.zip"; 52*4e2b41f1SAndroid Build Coastguard Worker 53*4e2b41f1SAndroid Build Coastguard Worker private static final String REMOUNT_TEST_PATH = "/system/remount_test"; 54*4e2b41f1SAndroid Build Coastguard Worker private static final String REMOUNT_TEST_FILE = REMOUNT_TEST_PATH + "/test_file"; 55*4e2b41f1SAndroid Build Coastguard Worker 56*4e2b41f1SAndroid Build Coastguard Worker @Option( 57*4e2b41f1SAndroid Build Coastguard Worker name = "wipe-dsu-on-failure", 58*4e2b41f1SAndroid Build Coastguard Worker description = "Wipe the DSU installation on test failure.") 59*4e2b41f1SAndroid Build Coastguard Worker private boolean mWipeDsuOnFailure = true; 60*4e2b41f1SAndroid Build Coastguard Worker 61*4e2b41f1SAndroid Build Coastguard Worker @Option( 62*4e2b41f1SAndroid Build Coastguard Worker name = "system-image-path", 63*4e2b41f1SAndroid Build Coastguard Worker description = "Path to the GSI system.img or directory containing the system.img.", 64*4e2b41f1SAndroid Build Coastguard Worker mandatory = true) 65*4e2b41f1SAndroid Build Coastguard Worker private File mSystemImagePath; 66*4e2b41f1SAndroid Build Coastguard Worker 67*4e2b41f1SAndroid Build Coastguard Worker private File mSystemImageZip; 68*4e2b41f1SAndroid Build Coastguard Worker getDsuInstallCommand()69*4e2b41f1SAndroid Build Coastguard Worker private String getDsuInstallCommand() { 70*4e2b41f1SAndroid Build Coastguard Worker return String.format( 71*4e2b41f1SAndroid Build Coastguard Worker "am start-activity" 72*4e2b41f1SAndroid Build Coastguard Worker + " -n com.android.dynsystem/com.android.dynsystem.VerificationActivity" 73*4e2b41f1SAndroid Build Coastguard Worker + " -a android.os.image.action.START_INSTALL" 74*4e2b41f1SAndroid Build Coastguard Worker + " -d file://%s" 75*4e2b41f1SAndroid Build Coastguard Worker + " --el KEY_USERDATA_SIZE %d" 76*4e2b41f1SAndroid Build Coastguard Worker + " --ez KEY_ENABLE_WHEN_COMPLETED true", 77*4e2b41f1SAndroid Build Coastguard Worker DSU_IMAGE_ZIP_PUSH_PATH, getDsuUserdataSize(DSU_DEFAULT_USERDATA_SIZE)); 78*4e2b41f1SAndroid Build Coastguard Worker } 79*4e2b41f1SAndroid Build Coastguard Worker 80*4e2b41f1SAndroid Build Coastguard Worker @Before setUp()81*4e2b41f1SAndroid Build Coastguard Worker public void setUp() throws IOException { 82*4e2b41f1SAndroid Build Coastguard Worker mSystemImageZip = null; 83*4e2b41f1SAndroid Build Coastguard Worker InputStream stream = null; 84*4e2b41f1SAndroid Build Coastguard Worker try { 85*4e2b41f1SAndroid Build Coastguard Worker assertNotNull("--system-image-path is invalid", mSystemImagePath); 86*4e2b41f1SAndroid Build Coastguard Worker if (mSystemImagePath.isDirectory()) { 87*4e2b41f1SAndroid Build Coastguard Worker File gsiImageFile = FileUtil.findFile(mSystemImagePath, GSI_IMAGE_NAME); 88*4e2b41f1SAndroid Build Coastguard Worker assertNotNull("Cannot find " + GSI_IMAGE_NAME, gsiImageFile); 89*4e2b41f1SAndroid Build Coastguard Worker stream = new FileInputStream(gsiImageFile); 90*4e2b41f1SAndroid Build Coastguard Worker } else { 91*4e2b41f1SAndroid Build Coastguard Worker stream = new FileInputStream(mSystemImagePath); 92*4e2b41f1SAndroid Build Coastguard Worker } 93*4e2b41f1SAndroid Build Coastguard Worker stream = new BufferedInputStream(stream); 94*4e2b41f1SAndroid Build Coastguard Worker mSystemImageZip = FileUtil.createTempFile(this.getClass().getSimpleName(), "gsi.zip"); 95*4e2b41f1SAndroid Build Coastguard Worker try (FileOutputStream foStream = new FileOutputStream(mSystemImageZip); 96*4e2b41f1SAndroid Build Coastguard Worker BufferedOutputStream boStream = new BufferedOutputStream(foStream); 97*4e2b41f1SAndroid Build Coastguard Worker ZipOutputStream out = new ZipOutputStream(boStream); ) { 98*4e2b41f1SAndroid Build Coastguard Worker // Don't bother compressing it as we are going to uncompress it on device anyway. 99*4e2b41f1SAndroid Build Coastguard Worker out.setLevel(0); 100*4e2b41f1SAndroid Build Coastguard Worker out.putNextEntry(new ZipEntry(GSI_IMAGE_NAME)); 101*4e2b41f1SAndroid Build Coastguard Worker StreamUtil.copyStreams(stream, out); 102*4e2b41f1SAndroid Build Coastguard Worker out.closeEntry(); 103*4e2b41f1SAndroid Build Coastguard Worker } 104*4e2b41f1SAndroid Build Coastguard Worker } finally { 105*4e2b41f1SAndroid Build Coastguard Worker StreamUtil.close(stream); 106*4e2b41f1SAndroid Build Coastguard Worker } 107*4e2b41f1SAndroid Build Coastguard Worker } 108*4e2b41f1SAndroid Build Coastguard Worker 109*4e2b41f1SAndroid Build Coastguard Worker @After tearDown()110*4e2b41f1SAndroid Build Coastguard Worker public void tearDown() { 111*4e2b41f1SAndroid Build Coastguard Worker try { 112*4e2b41f1SAndroid Build Coastguard Worker FileUtil.deleteFile(mSystemImageZip); 113*4e2b41f1SAndroid Build Coastguard Worker } catch (SecurityException e) { 114*4e2b41f1SAndroid Build Coastguard Worker CLog.w("Failed to clean up '%s': %s", mSystemImageZip, e); 115*4e2b41f1SAndroid Build Coastguard Worker } 116*4e2b41f1SAndroid Build Coastguard Worker if (mWipeDsuOnFailure) { 117*4e2b41f1SAndroid Build Coastguard Worker // If test case completed successfully, then the test body should have called `wipe` 118*4e2b41f1SAndroid Build Coastguard Worker // already and calling `wipe` again would be a noop. 119*4e2b41f1SAndroid Build Coastguard Worker // If test case failed, then this piece of code would clean up the DSU installation left 120*4e2b41f1SAndroid Build Coastguard Worker // by the failed test case. 121*4e2b41f1SAndroid Build Coastguard Worker try { 122*4e2b41f1SAndroid Build Coastguard Worker getDevice().executeShellV2Command("gsi_tool wipe"); 123*4e2b41f1SAndroid Build Coastguard Worker if (isDsuRunning()) { 124*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 125*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 126*4e2b41f1SAndroid Build Coastguard Worker } 127*4e2b41f1SAndroid Build Coastguard Worker } catch (DeviceNotAvailableException e) { 128*4e2b41f1SAndroid Build Coastguard Worker CLog.w("Failed to clean up DSU installation on device: %s", e); 129*4e2b41f1SAndroid Build Coastguard Worker } 130*4e2b41f1SAndroid Build Coastguard Worker } 131*4e2b41f1SAndroid Build Coastguard Worker try { 132*4e2b41f1SAndroid Build Coastguard Worker getDevice().deleteFile(DSU_IMAGE_ZIP_PUSH_PATH); 133*4e2b41f1SAndroid Build Coastguard Worker } catch (DeviceNotAvailableException e) { 134*4e2b41f1SAndroid Build Coastguard Worker CLog.w("Failed to clean up device '%s': %s", DSU_IMAGE_ZIP_PUSH_PATH, e); 135*4e2b41f1SAndroid Build Coastguard Worker } 136*4e2b41f1SAndroid Build Coastguard Worker } 137*4e2b41f1SAndroid Build Coastguard Worker 138*4e2b41f1SAndroid Build Coastguard Worker @Test testDsuGsi()139*4e2b41f1SAndroid Build Coastguard Worker public void testDsuGsi() throws DeviceNotAvailableException { 140*4e2b41f1SAndroid Build Coastguard Worker if (isDsuRunning()) { 141*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Wipe existing DSU installation"); 142*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("gsi_tool wipe"); 143*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 144*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 145*4e2b41f1SAndroid Build Coastguard Worker assertDsuNotRunning(); 146*4e2b41f1SAndroid Build Coastguard Worker } 147*4e2b41f1SAndroid Build Coastguard Worker 148*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Pushing '%s' -> '%s'", mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH); 149*4e2b41f1SAndroid Build Coastguard Worker getDevice().pushFile(mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH, true); 150*4e2b41f1SAndroid Build Coastguard Worker 151*4e2b41f1SAndroid Build Coastguard Worker final long freeSpaceBeforeInstall = getDevice().getPartitionFreeSpace("/data") << 10; 152*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand(getDsuInstallCommand()); 153*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Wait for DSU installation complete and reboot"); 154*4e2b41f1SAndroid Build Coastguard Worker assertTrue( 155*4e2b41f1SAndroid Build Coastguard Worker "Timed out waiting for DSU installation complete", 156*4e2b41f1SAndroid Build Coastguard Worker getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)); 157*4e2b41f1SAndroid Build Coastguard Worker CLog.i("DSU installation is complete and device is disconnected"); 158*4e2b41f1SAndroid Build Coastguard Worker 159*4e2b41f1SAndroid Build Coastguard Worker getDevice().waitForDeviceAvailable(); 160*4e2b41f1SAndroid Build Coastguard Worker assertDsuRunning(); 161*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Successfully booted with DSU"); 162*4e2b41f1SAndroid Build Coastguard Worker 163*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Test 'gsi_tool enable -s' and 'gsi_tool enable'"); 164*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 165*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 166*4e2b41f1SAndroid Build Coastguard Worker assertDsuNotRunning(); 167*4e2b41f1SAndroid Build Coastguard Worker 168*4e2b41f1SAndroid Build Coastguard Worker final long freeSpaceAfterInstall = getDevice().getPartitionFreeSpace("/data") << 10; 169*4e2b41f1SAndroid Build Coastguard Worker final long estimatedDsuSize = freeSpaceBeforeInstall - freeSpaceAfterInstall; 170*4e2b41f1SAndroid Build Coastguard Worker assertTrue( 171*4e2b41f1SAndroid Build Coastguard Worker String.format( 172*4e2b41f1SAndroid Build Coastguard Worker "Expected DSU installation to consume some storage space, free space before" 173*4e2b41f1SAndroid Build Coastguard Worker + " install: %d, free space after install: %d, delta: %d", 174*4e2b41f1SAndroid Build Coastguard Worker freeSpaceBeforeInstall, freeSpaceAfterInstall, estimatedDsuSize), 175*4e2b41f1SAndroid Build Coastguard Worker estimatedDsuSize > 0); 176*4e2b41f1SAndroid Build Coastguard Worker 177*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("gsi_tool enable"); 178*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 179*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 180*4e2b41f1SAndroid Build Coastguard Worker assertDsuRunning(); 181*4e2b41f1SAndroid Build Coastguard Worker 182*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Set up 'adb remount' for testing (requires reboot)"); 183*4e2b41f1SAndroid Build Coastguard Worker assertAdbRoot(); 184*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("remount"); 185*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 186*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 187*4e2b41f1SAndroid Build Coastguard Worker assertDsuRunning(); 188*4e2b41f1SAndroid Build Coastguard Worker assertAdbRoot(); 189*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("remount"); 190*4e2b41f1SAndroid Build Coastguard Worker assertDevicePathNotExist(REMOUNT_TEST_PATH); 191*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand(String.format("mkdir -p '%s'", REMOUNT_TEST_PATH)); 192*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand(String.format("cp /system/bin/init '%s'", REMOUNT_TEST_FILE)); 193*4e2b41f1SAndroid Build Coastguard Worker final String initContent = getDevice().pullFileContents("/system/bin/init"); 194*4e2b41f1SAndroid Build Coastguard Worker 195*4e2b41f1SAndroid Build Coastguard Worker CLog.i("DSU and original system have separate remount overlays"); 196*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("gsi_tool disable"); 197*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 198*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 199*4e2b41f1SAndroid Build Coastguard Worker assertDsuNotRunning(); 200*4e2b41f1SAndroid Build Coastguard Worker assertDevicePathNotExist(REMOUNT_TEST_PATH); 201*4e2b41f1SAndroid Build Coastguard Worker 202*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Test that 'adb remount' is consistent after reboot"); 203*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("gsi_tool enable"); 204*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 205*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 206*4e2b41f1SAndroid Build Coastguard Worker assertDsuRunning(); 207*4e2b41f1SAndroid Build Coastguard Worker assertDevicePathExist(REMOUNT_TEST_FILE); 208*4e2b41f1SAndroid Build Coastguard Worker assertEquals( 209*4e2b41f1SAndroid Build Coastguard Worker String.format( 210*4e2b41f1SAndroid Build Coastguard Worker "Expected contents of '%s' to persist after reboot", REMOUNT_TEST_FILE), 211*4e2b41f1SAndroid Build Coastguard Worker initContent, 212*4e2b41f1SAndroid Build Coastguard Worker getDevice().pullFileContents(REMOUNT_TEST_FILE)); 213*4e2b41f1SAndroid Build Coastguard Worker 214*4e2b41f1SAndroid Build Coastguard Worker CLog.i("'enable-verity' should teardown the remount overlay and restore the filesystem"); 215*4e2b41f1SAndroid Build Coastguard Worker assertAdbRoot(); 216*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("enable-verity"); 217*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 218*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 219*4e2b41f1SAndroid Build Coastguard Worker assertDsuRunning(); 220*4e2b41f1SAndroid Build Coastguard Worker assertDevicePathNotExist(REMOUNT_TEST_PATH); 221*4e2b41f1SAndroid Build Coastguard Worker 222*4e2b41f1SAndroid Build Coastguard Worker CLog.i("Test 'gsi_tool wipe'"); 223*4e2b41f1SAndroid Build Coastguard Worker assertShellCommand("gsi_tool wipe"); 224*4e2b41f1SAndroid Build Coastguard Worker getDevice().reboot(); 225*4e2b41f1SAndroid Build Coastguard Worker getDevice().disableAdbRoot(); 226*4e2b41f1SAndroid Build Coastguard Worker assertDsuNotRunning(); 227*4e2b41f1SAndroid Build Coastguard Worker 228*4e2b41f1SAndroid Build Coastguard Worker final double dampeningCoefficient = 0.9; 229*4e2b41f1SAndroid Build Coastguard Worker final long freeSpaceAfterWipe = getDevice().getPartitionFreeSpace("/data") << 10; 230*4e2b41f1SAndroid Build Coastguard Worker final long freeSpaceReturnedByWipe = freeSpaceAfterWipe - freeSpaceAfterInstall; 231*4e2b41f1SAndroid Build Coastguard Worker assertTrue( 232*4e2b41f1SAndroid Build Coastguard Worker String.format( 233*4e2b41f1SAndroid Build Coastguard Worker "Expected 'gsi_tool wipe' to return roughly %d of storage space, free space" 234*4e2b41f1SAndroid Build Coastguard Worker + " before wipe: %d, free space after wipe: %d, delta: %d", 235*4e2b41f1SAndroid Build Coastguard Worker estimatedDsuSize, 236*4e2b41f1SAndroid Build Coastguard Worker freeSpaceAfterInstall, 237*4e2b41f1SAndroid Build Coastguard Worker freeSpaceAfterWipe, 238*4e2b41f1SAndroid Build Coastguard Worker freeSpaceReturnedByWipe), 239*4e2b41f1SAndroid Build Coastguard Worker freeSpaceReturnedByWipe > (long) (estimatedDsuSize * dampeningCoefficient)); 240*4e2b41f1SAndroid Build Coastguard Worker } 241*4e2b41f1SAndroid Build Coastguard Worker } 242