summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp5
-rw-r--r--OWNERS1
-rw-r--r--aidl/android/gsi/IImageService.aidl7
-rw-r--r--daemon.cpp2
-rw-r--r--gsi_service.cpp17
-rw-r--r--gsi_service.h2
-rw-r--r--gsi_tool.cpp3
-rw-r--r--include/libgsi/libgsi.h2
-rw-r--r--include/libgsi/libgsid.h4
-rw-r--r--libgsid.cpp2
-rw-r--r--partition_installer.cpp7
-rw-r--r--tests/Android.bp33
-rw-r--r--tests/DSUEndtoEndTest.java53
-rw-r--r--tests/DsuGsiIntegrationTest.java232
-rw-r--r--tests/DsuTestBase.java89
-rw-r--r--tests/dsu_gsi_integration_test.xml22
16 files changed, 438 insertions, 43 deletions
diff --git a/Android.bp b/Android.bp
index 50618f9..3e63fe7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -74,6 +74,11 @@ cc_library_headers {
recovery_available: true,
vendor_available: true,
export_include_dirs: ["include"],
+ apex_available: [
+ "//apex_available:anyapex",
+ "//apex_available:platform",
+ ],
+ min_sdk_version: "31",
}
cc_binary {
diff --git a/OWNERS b/OWNERS
index 09f4d71..a9d0479 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,3 +2,4 @@
dvander@google.com
sspatil@google.com
elsk@google.com
+yochiang@google.com
diff --git a/aidl/android/gsi/IImageService.aidl b/aidl/android/gsi/IImageService.aidl
index 363a919..9f84e50 100644
--- a/aidl/android/gsi/IImageService.aidl
+++ b/aidl/android/gsi/IImageService.aidl
@@ -130,7 +130,12 @@ interface IImageService {
void removeAllImages();
/**
- * Remove all images that were marked as disabled in recovery.
+ * Mark an image as disabled.
+ */
+ void disableImage(@utf8InCpp String name);
+
+ /**
+ * Remove all images that were marked as disabled.
*/
void removeDisabledImages();
diff --git a/daemon.cpp b/daemon.cpp
index 2123599..33e7705 100644
--- a/daemon.cpp
+++ b/daemon.cpp
@@ -65,7 +65,7 @@ int main(int argc, char** argv) {
}
}
- android::gsi::GsiService::Register();
+ android::gsi::GsiService::Register(android::gsi::kGsiServiceName);
{
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
diff --git a/gsi_service.cpp b/gsi_service.cpp
index 3392a1d..2caa445 100644
--- a/gsi_service.cpp
+++ b/gsi_service.cpp
@@ -87,10 +87,10 @@ GsiService::GsiService() {
progress_ = {};
}
-void GsiService::Register() {
+void GsiService::Register(const std::string& name) {
auto lazyRegistrar = LazyServiceRegistrar::getInstance();
android::sp<GsiService> service = new GsiService();
- auto ret = lazyRegistrar.registerService(service, kGsiServiceName);
+ auto ret = lazyRegistrar.registerService(service, name);
if (ret != android::OK) {
LOG(FATAL) << "Could not register gsi service: " << ret;
@@ -343,7 +343,7 @@ binder::Status GsiService::isGsiEnabled(bool* _aidl_return) {
}
binder::Status GsiService::removeGsiAsync(const sp<IGsiServiceCallback>& resultCallback) {
- bool result;
+ bool result = false;
auto status = removeGsi(&result);
if (!status.isOk()) {
LOG(ERROR) << "Could not removeGsi: " << status.exceptionMessage().string();
@@ -577,6 +577,7 @@ class ImageService : public BinderService<ImageService>, public BnImageService {
int32_t* _aidl_return) override;
binder::Status zeroFillNewImage(const std::string& name, int64_t bytes) override;
binder::Status removeAllImages() override;
+ binder::Status disableImage(const std::string& name) override;
binder::Status removeDisabledImages() override;
binder::Status getMappedImageDevice(const std::string& name, std::string* device) override;
binder::Status isImageDisabled(const std::string& name, bool* _aidl_return) override;
@@ -740,6 +741,16 @@ binder::Status ImageService::removeAllImages() {
return binder::Status::ok();
}
+binder::Status ImageService::disableImage(const std::string& name) {
+ if (!CheckUid()) return UidSecurityError();
+
+ std::lock_guard<std::mutex> guard(service_->lock());
+ if (!impl_->DisableImage(name)) {
+ return BinderError("Failed to disable image: " + name);
+ }
+ return binder::Status::ok();
+}
+
binder::Status ImageService::removeDisabledImages() {
if (!CheckUid()) return UidSecurityError();
diff --git a/gsi_service.h b/gsi_service.h
index c50c101..3c4c278 100644
--- a/gsi_service.h
+++ b/gsi_service.h
@@ -36,7 +36,7 @@ namespace gsi {
class GsiService : public BinderService<GsiService>, public BnGsiService {
public:
- static void Register();
+ static void Register(const std::string& name);
binder::Status openInstall(const std::string& install_dir, int* _aidl_return) override;
binder::Status closeInstall(int32_t* _aidl_return) override;
diff --git a/gsi_tool.cpp b/gsi_tool.cpp
index a6a79a5..181452b 100644
--- a/gsi_tool.cpp
+++ b/gsi_tool.cpp
@@ -35,6 +35,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <android/gsi/IGsiService.h>
+#include <binder/ProcessState.h>
#include <cutils/android_reboot.h>
#include <libgsi/libgsi.h>
#include <libgsi/libgsid.h>
@@ -714,6 +715,8 @@ static int usage(int /* argc */, char* argv[]) {
int main(int argc, char** argv) {
android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+ // Start a threadpool to service waitForService() callbacks.
+ android::ProcessState::self()->startThreadPool();
android::sp<IGsiService> service = GetGsiService();
if (!service) {
return EX_SOFTWARE;
diff --git a/include/libgsi/libgsi.h b/include/libgsi/libgsi.h
index 41898df..5318341 100644
--- a/include/libgsi/libgsi.h
+++ b/include/libgsi/libgsi.h
@@ -24,8 +24,6 @@
namespace android {
namespace gsi {
-static constexpr char kGsiServiceName[] = "gsiservice";
-
#define DSU_METADATA_PREFIX "/metadata/gsi/dsu/"
// These files need to be globally readable so that fs_mgr_fstab, which is
diff --git a/include/libgsi/libgsid.h b/include/libgsi/libgsid.h
index 46f8ef5..1229ce7 100644
--- a/include/libgsi/libgsid.h
+++ b/include/libgsi/libgsid.h
@@ -21,6 +21,10 @@
namespace android {
namespace gsi {
+static constexpr char kGsiServiceName[] = "gsiservice";
+
+// Caller should start a threadpool before calling this method, otherwise waitForService() could
+// block for at least 1 second.
android::sp<IGsiService> GetGsiService();
} // namespace gsi
diff --git a/libgsid.cpp b/libgsid.cpp
index 23eeae4..4689dcd 100644
--- a/libgsid.cpp
+++ b/libgsid.cpp
@@ -17,7 +17,7 @@
#include <android-base/logging.h>
#include <android/gsi/IGsiService.h>
#include <binder/IServiceManager.h>
-#include <libgsi/libgsi.h>
+#include <libgsi/libgsid.h>
namespace android {
namespace gsi {
diff --git a/partition_installer.cpp b/partition_installer.cpp
index 5450169..ebd3b64 100644
--- a/partition_installer.cpp
+++ b/partition_installer.cpp
@@ -20,6 +20,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
@@ -350,8 +351,10 @@ int PartitionInstaller::WipeWritable(const std::string& active_dsu, const std::s
std::optional<uint64_t> PartitionInstaller::GetMinimumFreeSpaceThreshold(
const std::string& install_dir) {
- // No need to retain any space if we were not installing to the internal storage.
- if (!android::base::StartsWith(install_dir, "/data"s)) {
+ // No need to retain any space if we were not installing to the internal storage
+ // or device is not using VAB.
+ if (!android::base::StartsWith(install_dir, "/data"s)
+ || !android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
return 0;
}
// Dynamic Partitions device must have a "super" block device.
diff --git a/tests/Android.bp b/tests/Android.bp
index 5d90fd4..3035c10 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -57,10 +57,43 @@ cc_test {
require_root: true,
}
+java_library_host {
+ name: "DsuTestBase",
+ srcs: [
+ "DsuTestBase.java",
+ ],
+ libs: [
+ "tradefed",
+ ],
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
java_test_host {
name: "DSUEndtoEndTest",
srcs: ["DSUEndtoEndTest.java"],
+ static_libs: [
+ "DsuTestBase",
+ ],
libs: ["tradefed"],
test_config: "dsu-test.xml",
test_suites: ["general-tests"],
}
+
+java_test_host {
+ name: "DsuGsiIntegrationTest",
+ srcs: [
+ "DsuGsiIntegrationTest.java",
+ ],
+ static_libs: [
+ "DsuTestBase",
+ ],
+ libs: [
+ "tradefed",
+ ],
+ test_config: "dsu_gsi_integration_test.xml",
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/tests/DSUEndtoEndTest.java b/tests/DSUEndtoEndTest.java
index e717079..79799bf 100644
--- a/tests/DSUEndtoEndTest.java
+++ b/tests/DSUEndtoEndTest.java
@@ -16,19 +16,14 @@
package com.android.tests.dsu;
-import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.ZipUtil2;
import org.apache.commons.compress.archivers.zip.ZipFile;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
@@ -36,16 +31,13 @@ import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.IOException;
-import java.lang.Process;
-import java.lang.Runtime;
import java.util.concurrent.TimeUnit;
/**
* Test Dynamic System Updates by booting in and out of a supplied system image
*/
@RunWith(DeviceJUnit4ClassRunner.class)
-public class DSUEndtoEndTest extends BaseHostJUnit4Test {
+public class DSUEndtoEndTest extends DsuTestBase {
private static final long kDefaultUserdataSize = 4L * 1024 * 1024 * 1024;
private static final String LPUNPACK_PATH = "bin/lpunpack";
private static final String SIMG2IMG_PATH = "bin/simg2img";
@@ -59,12 +51,6 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test {
importance=Importance.ALWAYS)
private String mSystemImagePath;
- @Option(name="userdata_size",
- shortName='u',
- description="size in bytes of the new userdata partition",
- importance=Importance.ALWAYS)
- private long mUserdataSize = kDefaultUserdataSize;
-
private File mUnsparseSystemImage;
@After
@@ -121,47 +107,50 @@ public class DSUEndtoEndTest extends BaseHostJUnit4Test {
if (!wasRoot)
Assert.assertTrue("Test requires root", getDevice().enableAdbRoot());
- expectGsiStatus("normal");
+ assertDsuStatus("normal");
// Sleep after installing to allow time for gsi_tool to reboot. This prevents a race between
// the device rebooting and waitForDeviceAvailable() returning.
- getDevice().executeShellV2Command("gsi_tool install --userdata-size " + mUserdataSize +
- " --gsi-size " + gsi.length() + " && sleep 10000000", gsi, null, 10, TimeUnit.MINUTES, 1);
+ getDevice()
+ .executeShellV2Command(
+ String.format(
+ "gsi_tool install --userdata-size %d"
+ + " --gsi-size %d"
+ + " && sleep 10000000",
+ getDsuUserdataSize(kDefaultUserdataSize), gsi.length()),
+ gsi,
+ null,
+ 10,
+ TimeUnit.MINUTES,
+ 1);
getDevice().waitForDeviceAvailable();
getDevice().enableAdbRoot();
- expectGsiStatus("running");
+ assertDsuStatus("running");
getDevice().rebootUntilOnline();
- expectGsiStatus("installed");
+ assertDsuStatus("installed");
- CommandResult result = getDevice().executeShellV2Command("gsi_tool enable");
- Assert.assertEquals("gsi_tool enable failed", 0, result.getExitCode().longValue());
+ assertShellCommand("gsi_tool enable");
getDevice().reboot();
- expectGsiStatus("running");
+ assertDsuStatus("running");
getDevice().reboot();
- expectGsiStatus("running");
+ assertDsuStatus("running");
- getDevice().executeShellV2Command("gsi_tool wipe");
+ assertShellCommand("gsi_tool wipe");
getDevice().rebootUntilOnline();
- expectGsiStatus("normal");
+ assertDsuStatus("normal");
if (wasRoot) {
getDevice().enableAdbRoot();
}
}
-
- private void expectGsiStatus(String expected) throws Exception {
- CommandResult result = getDevice().executeShellV2Command("gsi_tool status");
- String status = result.getStdout().split("\n", 2)[0].trim();
- Assert.assertEquals("Device not in expected DSU state", expected, status);
- }
}
diff --git a/tests/DsuGsiIntegrationTest.java b/tests/DsuGsiIntegrationTest.java
new file mode 100644
index 0000000..78d001c
--- /dev/null
+++ b/tests/DsuGsiIntegrationTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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));
+ }
+}
diff --git a/tests/DsuTestBase.java b/tests/DsuTestBase.java
new file mode 100644
index 0000000..db383be
--- /dev/null
+++ b/tests/DsuTestBase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.assertFalse;
+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.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+abstract class DsuTestBase extends BaseHostJUnit4Test {
+ @Option(
+ name = "dsu-userdata-size-in-gb",
+ description = "Userdata partition size of the DSU installation")
+ private long mDsuUserdataSizeInGb;
+
+ protected long getDsuUserdataSize(long defaultValue) {
+ return mDsuUserdataSizeInGb > 0 ? mDsuUserdataSizeInGb << 30 : defaultValue;
+ }
+
+ public CommandResult assertShellCommand(String command) throws DeviceNotAvailableException {
+ CommandResult result = getDevice().executeShellV2Command(command);
+ assertEquals(
+ String.format("'%s' status", command), CommandStatus.SUCCESS, result.getStatus());
+ assertNotNull(String.format("'%s' exit code", command), result.getExitCode());
+ assertEquals(String.format("'%s' exit code", command), 0, result.getExitCode().intValue());
+ return result;
+ }
+
+ private String getDsuStatus() throws DeviceNotAvailableException {
+ CommandResult result;
+ try {
+ result = assertShellCommand("gsi_tool status");
+ } catch (AssertionError e) {
+ CLog.e(e);
+ return null;
+ }
+ return result.getStdout().split("\n", 2)[0].trim();
+ }
+
+ public void assertDsuStatus(String expected) throws DeviceNotAvailableException {
+ assertEquals("DSU status", expected, getDsuStatus());
+ }
+
+ public boolean isDsuRunning() throws DeviceNotAvailableException {
+ return "running".equals(getDsuStatus());
+ }
+
+ public void assertDsuRunning() throws DeviceNotAvailableException {
+ assertTrue("Expected DSU running", isDsuRunning());
+ }
+
+ public void assertDsuNotRunning() throws DeviceNotAvailableException {
+ assertFalse("Expected DSU not running", isDsuRunning());
+ }
+
+ public void assertAdbRoot() throws DeviceNotAvailableException {
+ assertTrue("Failed to 'adb root'", getDevice().enableAdbRoot());
+ }
+
+ public void assertDevicePathExist(String path) throws DeviceNotAvailableException {
+ assertTrue(String.format("Expected '%s' to exist", path), getDevice().doesFileExist(path));
+ }
+
+ public void assertDevicePathNotExist(String path) throws DeviceNotAvailableException {
+ assertFalse(
+ String.format("Expected '%s' to not exist", path), getDevice().doesFileExist(path));
+ }
+}
diff --git a/tests/dsu_gsi_integration_test.xml b/tests/dsu_gsi_integration_test.xml
new file mode 100644
index 0000000..b8c74ef
--- /dev/null
+++ b/tests/dsu_gsi_integration_test.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs DsuGsiIntegrationTest">
+ <option name="test-suite-tag" value="dsu-gsi-integration-test" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.tests.dsu.DsuGsiIntegrationTest" />
+ </test>
+</configuration>
+