diff options
-rw-r--r-- | Android.bp | 5 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | aidl/android/gsi/IImageService.aidl | 7 | ||||
-rw-r--r-- | daemon.cpp | 2 | ||||
-rw-r--r-- | gsi_service.cpp | 17 | ||||
-rw-r--r-- | gsi_service.h | 2 | ||||
-rw-r--r-- | gsi_tool.cpp | 3 | ||||
-rw-r--r-- | include/libgsi/libgsi.h | 2 | ||||
-rw-r--r-- | include/libgsi/libgsid.h | 4 | ||||
-rw-r--r-- | libgsid.cpp | 2 | ||||
-rw-r--r-- | partition_installer.cpp | 7 | ||||
-rw-r--r-- | tests/Android.bp | 33 | ||||
-rw-r--r-- | tests/DSUEndtoEndTest.java | 53 | ||||
-rw-r--r-- | tests/DsuGsiIntegrationTest.java | 232 | ||||
-rw-r--r-- | tests/DsuTestBase.java | 89 | ||||
-rw-r--r-- | tests/dsu_gsi_integration_test.xml | 22 |
16 files changed, 438 insertions, 43 deletions
@@ -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 { @@ -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(); @@ -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> + |