233 lines
9.5 KiB
Java
233 lines
9.5 KiB
Java
/*
|
|
* Copyright (C) 2022 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.dsu;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
import com.android.tradefed.config.Option;
|
|
import com.android.tradefed.device.DeviceNotAvailableException;
|
|
import com.android.tradefed.log.LogUtil.CLog;
|
|
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
|
|
import com.android.tradefed.util.FileUtil;
|
|
import com.android.tradefed.util.StreamUtil;
|
|
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
@RunWith(DeviceJUnit4ClassRunner.class)
|
|
public class DsuGsiIntegrationTest extends DsuTestBase {
|
|
private static final long DSU_MAX_WAIT_SEC = 10 * 60;
|
|
private static final long DSU_DEFAULT_USERDATA_SIZE = 8L << 30;
|
|
|
|
private static final String GSI_IMAGE_NAME = "system.img";
|
|
private static final String DSU_IMAGE_ZIP_PUSH_PATH = "/sdcard/gsi.zip";
|
|
|
|
private static final String REMOUNT_TEST_PATH = "/system/remount_test";
|
|
private static final String REMOUNT_TEST_FILE = REMOUNT_TEST_PATH + "/test_file";
|
|
|
|
@Option(
|
|
name = "wipe-dsu-on-failure",
|
|
description = "Wipe the DSU installation on test failure.")
|
|
private boolean mWipeDsuOnFailure = true;
|
|
|
|
@Option(
|
|
name = "system-image-path",
|
|
description = "Path to the GSI system.img or directory containing the system.img.",
|
|
mandatory = true)
|
|
private File mSystemImagePath;
|
|
|
|
private File mSystemImageZip;
|
|
|
|
private String getDsuInstallCommand() {
|
|
return String.format(
|
|
"am start-activity"
|
|
+ " -n com.android.dynsystem/com.android.dynsystem.VerificationActivity"
|
|
+ " -a android.os.image.action.START_INSTALL"
|
|
+ " -d file://%s"
|
|
+ " --el KEY_USERDATA_SIZE %d"
|
|
+ " --ez KEY_ENABLE_WHEN_COMPLETED true",
|
|
DSU_IMAGE_ZIP_PUSH_PATH, getDsuUserdataSize(DSU_DEFAULT_USERDATA_SIZE));
|
|
}
|
|
|
|
@Before
|
|
public void setUp() throws IOException {
|
|
mSystemImageZip = null;
|
|
InputStream stream = null;
|
|
try {
|
|
assertNotNull("--system-image-path is invalid", mSystemImagePath);
|
|
if (mSystemImagePath.isDirectory()) {
|
|
File gsiImageFile = FileUtil.findFile(mSystemImagePath, GSI_IMAGE_NAME);
|
|
assertNotNull("Cannot find " + GSI_IMAGE_NAME, gsiImageFile);
|
|
stream = new FileInputStream(gsiImageFile);
|
|
} else {
|
|
stream = new FileInputStream(mSystemImagePath);
|
|
}
|
|
stream = new BufferedInputStream(stream);
|
|
mSystemImageZip = FileUtil.createTempFile(this.getClass().getSimpleName(), "gsi.zip");
|
|
try (FileOutputStream foStream = new FileOutputStream(mSystemImageZip);
|
|
BufferedOutputStream boStream = new BufferedOutputStream(foStream);
|
|
ZipOutputStream out = new ZipOutputStream(boStream); ) {
|
|
// Don't bother compressing it as we are going to uncompress it on device anyway.
|
|
out.setLevel(0);
|
|
out.putNextEntry(new ZipEntry(GSI_IMAGE_NAME));
|
|
StreamUtil.copyStreams(stream, out);
|
|
out.closeEntry();
|
|
}
|
|
} finally {
|
|
StreamUtil.close(stream);
|
|
}
|
|
}
|
|
|
|
@After
|
|
public void tearDown() {
|
|
try {
|
|
FileUtil.deleteFile(mSystemImageZip);
|
|
} catch (SecurityException e) {
|
|
CLog.w("Failed to clean up '%s': %s", mSystemImageZip, e);
|
|
}
|
|
if (mWipeDsuOnFailure) {
|
|
// If test case completed successfully, then the test body should have called `wipe`
|
|
// already and calling `wipe` again would be a noop.
|
|
// If test case failed, then this piece of code would clean up the DSU installation left
|
|
// by the failed test case.
|
|
try {
|
|
getDevice().executeShellV2Command("gsi_tool wipe");
|
|
if (isDsuRunning()) {
|
|
getDevice().reboot();
|
|
}
|
|
} catch (DeviceNotAvailableException e) {
|
|
CLog.w("Failed to clean up DSU installation on device: %s", e);
|
|
}
|
|
}
|
|
try {
|
|
getDevice().deleteFile(DSU_IMAGE_ZIP_PUSH_PATH);
|
|
} catch (DeviceNotAvailableException e) {
|
|
CLog.w("Failed to clean up device '%s': %s", DSU_IMAGE_ZIP_PUSH_PATH, e);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testDsuGsi() throws DeviceNotAvailableException {
|
|
if (isDsuRunning()) {
|
|
CLog.i("Wipe existing DSU installation");
|
|
assertShellCommand("gsi_tool wipe");
|
|
getDevice().reboot();
|
|
assertDsuNotRunning();
|
|
}
|
|
|
|
CLog.i("Pushing '%s' -> '%s'", mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH);
|
|
getDevice().pushFile(mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH, true);
|
|
|
|
final long freeSpaceBeforeInstall = getDevice().getPartitionFreeSpace("/data") << 10;
|
|
assertShellCommand(getDsuInstallCommand());
|
|
CLog.i("Wait for DSU installation complete and reboot");
|
|
assertTrue(
|
|
"Timed out waiting for DSU installation complete",
|
|
getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000));
|
|
CLog.i("DSU installation is complete and device is disconnected");
|
|
|
|
getDevice().waitForDeviceAvailable();
|
|
assertDsuRunning();
|
|
CLog.i("Successfully booted with DSU");
|
|
|
|
CLog.i("Test 'gsi_tool enable -s' and 'gsi_tool enable'");
|
|
getDevice().reboot();
|
|
assertDsuNotRunning();
|
|
|
|
final long freeSpaceAfterInstall = getDevice().getPartitionFreeSpace("/data") << 10;
|
|
final long estimatedDsuSize = freeSpaceBeforeInstall - freeSpaceAfterInstall;
|
|
assertTrue(
|
|
String.format(
|
|
"Expected DSU installation to consume some storage space, free space before"
|
|
+ " install: %d, free space after install: %d, delta: %d",
|
|
freeSpaceBeforeInstall, freeSpaceAfterInstall, estimatedDsuSize),
|
|
estimatedDsuSize > 0);
|
|
|
|
assertShellCommand("gsi_tool enable");
|
|
getDevice().reboot();
|
|
assertDsuRunning();
|
|
|
|
CLog.i("Set up 'adb remount' for testing (requires reboot)");
|
|
assertAdbRoot();
|
|
assertShellCommand("remount");
|
|
getDevice().reboot();
|
|
assertDsuRunning();
|
|
assertAdbRoot();
|
|
assertShellCommand("remount");
|
|
assertDevicePathNotExist(REMOUNT_TEST_PATH);
|
|
assertShellCommand(String.format("mkdir -p '%s'", REMOUNT_TEST_PATH));
|
|
assertShellCommand(String.format("cp /system/bin/init '%s'", REMOUNT_TEST_FILE));
|
|
final String initContent = getDevice().pullFileContents("/system/bin/init");
|
|
|
|
CLog.i("DSU and original system have separate remount overlays");
|
|
assertShellCommand("gsi_tool disable");
|
|
getDevice().reboot();
|
|
assertDsuNotRunning();
|
|
assertDevicePathNotExist(REMOUNT_TEST_PATH);
|
|
|
|
CLog.i("Test that 'adb remount' is consistent after reboot");
|
|
assertShellCommand("gsi_tool enable");
|
|
getDevice().reboot();
|
|
assertDsuRunning();
|
|
assertDevicePathExist(REMOUNT_TEST_FILE);
|
|
assertEquals(
|
|
String.format(
|
|
"Expected contents of '%s' to persist after reboot", REMOUNT_TEST_FILE),
|
|
initContent,
|
|
getDevice().pullFileContents(REMOUNT_TEST_FILE));
|
|
|
|
CLog.i("'enable-verity' should teardown the remount overlay and restore the filesystem");
|
|
assertAdbRoot();
|
|
assertShellCommand("enable-verity");
|
|
getDevice().reboot();
|
|
assertDsuRunning();
|
|
assertDevicePathNotExist(REMOUNT_TEST_PATH);
|
|
|
|
CLog.i("Test 'gsi_tool wipe'");
|
|
assertShellCommand("gsi_tool wipe");
|
|
getDevice().reboot();
|
|
assertDsuNotRunning();
|
|
|
|
final double dampeningCoefficient = 0.9;
|
|
final long freeSpaceAfterWipe = getDevice().getPartitionFreeSpace("/data") << 10;
|
|
final long freeSpaceReturnedByWipe = freeSpaceAfterWipe - freeSpaceAfterInstall;
|
|
assertTrue(
|
|
String.format(
|
|
"Expected 'gsi_tool wipe' to return roughly %d of storage space, free space"
|
|
+ " before wipe: %d, free space after wipe: %d, delta: %d",
|
|
estimatedDsuSize,
|
|
freeSpaceAfterInstall,
|
|
freeSpaceAfterWipe,
|
|
freeSpaceReturnedByWipe),
|
|
freeSpaceReturnedByWipe > (long) (estimatedDsuSize * dampeningCoefficient));
|
|
}
|
|
}
|