Move AuthFsHostTest into VM
This change also drops test coverage for local file. It was only used
for development purpose, and is not worth to keep it work with a VM.
Bug: 191056545
Test: atest AuthFsHostTest on CF
Test: atest MicrodroidHostTestCases on CF
Change-Id: Ie2e3d8ecca00aee3cf518edeb3a81a9f59d9671c
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index bacb890..8061c56 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -10,6 +10,12 @@
"compatibility-tradefed",
"compatibility-host-util",
],
+ static_libs: [
+ "VirtualizationTestHelper",
+ ],
test_suites: ["general-tests"],
- data: [":authfs_test_files"],
+ data: [
+ ":authfs_test_files",
+ ":MicrodroidTestApp.signed",
+ ],
}
diff --git a/authfs/tests/AndroidTest.xml b/authfs/tests/AndroidTest.xml
index 485e392..8f940f6 100644
--- a/authfs/tests/AndroidTest.xml
+++ b/authfs/tests/AndroidTest.xml
@@ -15,10 +15,13 @@
-->
<configuration description="Config for authfs tests">
- <!-- Since Android does not support user namespace, we need root to access /dev/fuse and also
- to set up the mount. -->
+ <!-- Need root to start virtualizationservice -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <!-- virtualizationservice doesn't have access to shell_data_file. Instead of giving it
+ a test-only permission, run it without selinux -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
<!-- Basic checks that the device has all the prerequisites. -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="throw-if-cmd-fail" value="true" />
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 43d1210..426b333 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -18,19 +18,16 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.RootPermissionTest;
+import android.virt.test.VirtualizationTestCaseBase;
import com.android.compatibility.common.util.PollingCheck;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
import org.junit.After;
import org.junit.Before;
@@ -40,18 +37,22 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+// TODO move to Virtualization/tests/hostside/
@RootPermissionTest
@RunWith(DeviceJUnit4ClassRunner.class)
-public final class AuthFsHostTest extends BaseHostJUnit4Test {
+public final class AuthFsHostTest extends VirtualizationTestCaseBase {
- /** Test directory where data are located */
+ /** Test directory on Android where data are located */
private static final String TEST_DIR = "/data/local/tmp/authfs";
- /** Mount point of authfs during the test */
- private static final String MOUNT_DIR = "/data/local/tmp/authfs/mnt";
+ /** Mount point of authfs on Microdroid during the test */
+ private static final String MOUNT_DIR = "/data/local/tmp";
+ /** Path to fd_server on Android */
private static final String FD_SERVER_BIN = "/apex/com.android.virt/bin/fd_server";
- private static final String AUTHFS_BIN = "/apex/com.android.virt/bin/authfs";
+
+ /** Path to authfs on Microdroid */
+ private static final String AUTHFS_BIN = "/system/bin/authfs";
/** Plenty of time for authfs to get ready */
private static final int AUTHFS_INIT_TIMEOUT_MS = 1500;
@@ -59,68 +60,65 @@
/** FUSE's magic from statfs(2) */
private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
- private ITestDevice mDevice;
private ExecutorService mThreadPool;
+ private String mCid;
@Before
- public void setUp() {
- mDevice = getDevice();
+ public void setUp() throws DeviceNotAvailableException {
+ testIfDeviceIsCapable();
+
+ cleanUpTestFiles();
+
+ prepareVirtualizationTestSetup();
+
mThreadPool = Executors.newCachedThreadPool();
+
+ // For each test case, boot and adb connect to a new Microdroid
+ final String apkName = "MicrodroidTestApp.apk";
+ final String packageName = "com.android.microdroid.test";
+ final String configPath = "assets/vm_config.json"; // path inside the APK
+ mCid = startMicrodroid(apkName, packageName, configPath, /* debug */ false);
+ adbConnectToMicrodroid(mCid);
+
+ // Root because authfs (started from shell in this test) currently require root to open
+ // /dev/fuse and mount the FUSE.
+ rootMicrodroid();
}
@After
public void tearDown() throws DeviceNotAvailableException {
- mDevice.executeShellV2Command("killall authfs fd_server");
- mDevice.executeShellV2Command("umount " + MOUNT_DIR);
- mDevice.executeShellV2Command("rm -f " + TEST_DIR + "/output");
+ if (mCid != null) {
+ shutdownMicrodroid(mCid);
+ mCid = null;
+ }
+
+ tryRunOnAndroid("killall fd_server");
+ cleanUpTestFiles();
+ cleanUpVirtualizationTestSetup();
}
- @Test
- public void testReadWithFsverityVerification_LocalFile()
- throws DeviceNotAvailableException, InterruptedException {
- // Setup
- runAuthFsInBackground(
- "--local-ro-file-unverified 3:input.4m"
- + " --local-ro-file 4:input.4m:input.4m.merkle_dump:input.4m.fsv_sig:cert.der"
- + " --local-ro-file 5:input.4k1:input.4k1.merkle_dump:input.4k1.fsv_sig:cert.der"
- + " --local-ro-file 6:input.4k:input.4k.merkle_dump:input.4k.fsv_sig:cert.der"
- );
-
- // Action
- String actualHashUnverified4m = computeFileHashInGuest(MOUNT_DIR + "/3");
- String actualHash4m = computeFileHashInGuest(MOUNT_DIR + "/4");
- String actualHash4k1 = computeFileHashInGuest(MOUNT_DIR + "/5");
- String actualHash4k = computeFileHashInGuest(MOUNT_DIR + "/6");
-
- // Verify
- String expectedHash4m = computeFileHash(TEST_DIR + "/input.4m");
- String expectedHash4k1 = computeFileHash(TEST_DIR + "/input.4k1");
- String expectedHash4k = computeFileHash(TEST_DIR + "/input.4k");
-
- assertEquals("Inconsistent hash from /authfs/3: ", expectedHash4m, actualHashUnverified4m);
- assertEquals("Inconsistent hash from /authfs/4: ", expectedHash4m, actualHash4m);
- assertEquals("Inconsistent hash from /authfs/5: ", expectedHash4k1, actualHash4k1);
- assertEquals("Inconsistent hash from /authfs/6: ", expectedHash4k, actualHash4k);
+ private void cleanUpTestFiles() throws DeviceNotAvailableException {
+ tryRunOnAndroid("rm -f " + TEST_DIR + "/output");
}
@Test
public void testReadWithFsverityVerification_RemoteFile()
throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground(
+ runFdServerOnAndroid(
"3<input.4m 4<input.4m.merkle_dump 5<input.4m.fsv_sig 6<input.4m",
- "--ro-fds 3:4:5 --ro-fds 6"
- );
- runAuthFsInBackground(
+ "--ro-fds 3:4:5 --ro-fds 6 --rpc-binder");
+
+ runAuthFsOnMicrodroid(
"--remote-ro-file-unverified 10:6:4194304 --remote-ro-file 11:3:4194304:cert.der"
- );
+ + " --cid 2");
// Action
- String actualHashUnverified4m = computeFileHashInGuest(MOUNT_DIR + "/10");
- String actualHash4m = computeFileHashInGuest(MOUNT_DIR + "/11");
+ String actualHashUnverified4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
+ String actualHash4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/11");
// Verify
- String expectedHash4m = computeFileHash(TEST_DIR + "/input.4m");
+ String expectedHash4m = computeFileHashOnAndroid(TEST_DIR + "/input.4m");
assertEquals("Inconsistent hash from /authfs/10: ", expectedHash4m, actualHashUnverified4m);
assertEquals("Inconsistent hash from /authfs/11: ", expectedHash4m, actualHash4m);
@@ -132,22 +130,20 @@
public void testReadWithFsverityVerification_RemoteSmallerFile()
throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground(
+ runFdServerOnAndroid(
"3<input.4k 4<input.4k.merkle_dump 5<input.4k.fsv_sig"
- + " 6<input.4k1 7<input.4k1.merkle_dump 8<input.4k1.fsv_sig",
- "--ro-fds 3:4:5 --ro-fds 6:7:8"
- );
- runAuthFsInBackground(
- "--remote-ro-file 10:3:4096:cert.der --remote-ro-file 11:6:4097:cert.der"
- );
+ + " 6<input.4k1 7<input.4k1.merkle_dump 8<input.4k1.fsv_sig",
+ "--ro-fds 3:4:5 --ro-fds 6:7:8 --rpc-binder");
+ runAuthFsOnMicrodroid(
+ "--remote-ro-file 10:3:4096:cert.der --remote-ro-file 11:6:4097:cert.der --cid 2");
// Action
- String actualHash4k = computeFileHashInGuest(MOUNT_DIR + "/10");
- String actualHash4k1 = computeFileHashInGuest(MOUNT_DIR + "/11");
+ String actualHash4k = computeFileHashOnMicrodroid(MOUNT_DIR + "/10");
+ String actualHash4k1 = computeFileHashOnMicrodroid(MOUNT_DIR + "/11");
// Verify
- String expectedHash4k = computeFileHash(TEST_DIR + "/input.4k");
- String expectedHash4k1 = computeFileHash(TEST_DIR + "/input.4k1");
+ String expectedHash4k = computeFileHashOnAndroid(TEST_DIR + "/input.4k");
+ String expectedHash4k1 = computeFileHashOnAndroid(TEST_DIR + "/input.4k1");
assertEquals("Inconsistent hash from /authfs/10: ", expectedHash4k, actualHash4k);
assertEquals("Inconsistent hash from /authfs/11: ", expectedHash4k1, actualHash4k1);
@@ -157,31 +153,30 @@
public void testReadWithFsverityVerification_TamperedMerkleTree()
throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground(
+ runFdServerOnAndroid(
"3<input.4m 4<input.4m.merkle_dump.bad 5<input.4m.fsv_sig",
- "--ro-fds 3:4:5"
- );
- runAuthFsInBackground("--remote-ro-file 10:3:4096:cert.der");
+ "--ro-fds 3:4:5 --rpc-binder");
+ runAuthFsOnMicrodroid("--remote-ro-file 10:3:4096:cert.der --cid 2");
// Verify
- assertFalse(copyFileInGuest(MOUNT_DIR + "/10", "/dev/null"));
+ assertFalse(copyFileOnMicrodroid(MOUNT_DIR + "/10", "/dev/null"));
}
@Test
public void testWriteThroughCorrectly()
throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground("3<>output", "--rw-fds 3");
- runAuthFsInBackground("--remote-new-rw-file 20:3");
+ runFdServerOnAndroid("3<>output", "--rw-fds 3 --rpc-binder");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
// Action
- String srcPath = "/system/bin/linker";
+ String srcPath = "/system/bin/linker64";
String destPath = MOUNT_DIR + "/20";
String backendPath = TEST_DIR + "/output";
- assertTrue(copyFileInGuest(srcPath, destPath));
+ assertTrue(copyFileOnMicrodroid(srcPath, destPath));
// Verify
- String expectedHash = computeFileHashInGuest(srcPath);
+ String expectedHash = computeFileHashOnMicrodroid(srcPath);
expectBackingFileConsistency(destPath, backendPath, expectedHash);
}
@@ -189,60 +184,59 @@
public void testWriteFailedIfDetectsTampering()
throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground("3<>output", "--rw-fds 3");
- runAuthFsInBackground("--remote-new-rw-file 20:3");
+ runFdServerOnAndroid("3<>output", "--rw-fds 3 --rpc-binder");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
- String srcPath = "/system/bin/linker";
+ String srcPath = "/system/bin/linker64";
String destPath = MOUNT_DIR + "/20";
String backendPath = TEST_DIR + "/output";
- assertTrue(copyFileInGuest(srcPath, destPath));
+ assertTrue(copyFileOnMicrodroid(srcPath, destPath));
// Action
// Tampering with the first 2 4K block of the backing file.
- expectRemoteCommandToSucceed("dd if=/dev/zero of=" + backendPath + " bs=1 count=8192");
+ runOnAndroid("dd if=/dev/zero of=" + backendPath + " bs=1 count=8192");
// Verify
// Write to a block partially requires a read back to calculate the new hash. It should fail
// when the content is inconsistent to the known hash. Use direct I/O to avoid simply
// writing to the filesystem cache.
- expectRemoteCommandToFail("dd if=/dev/zero of=" + destPath + " bs=1 count=1024 direct");
+ assertEquals(
+ tryRunOnMicrodroid("dd if=/dev/zero of=" + destPath + " bs=1 count=1024 direct"),
+ null);
// A full 4K write does not require to read back, so write can succeed even if the backing
// block has already been tampered.
- expectRemoteCommandToSucceed(
- "dd if=/dev/zero of=" + destPath + " bs=1 count=4096 skip=4096");
+ runOnMicrodroid("dd if=/dev/zero of=" + destPath + " bs=1 count=4096 skip=4096");
// Otherwise, a partial write with correct backing file should still succeed.
- expectRemoteCommandToSucceed(
- "dd if=/dev/zero of=" + destPath + " bs=1 count=1024 skip=8192");
+ runOnMicrodroid("dd if=/dev/zero of=" + destPath + " bs=1 count=1024 skip=8192");
}
@Test
public void testFileResize() throws DeviceNotAvailableException, InterruptedException {
// Setup
- runFdServerInBackground("3<>output", "--rw-fds 3");
- runAuthFsInBackground("--remote-new-rw-file 20:3");
+ runFdServerOnAndroid("3<>output", "--rw-fds 3 --rpc-binder");
+ runAuthFsOnMicrodroid("--remote-new-rw-file 20:3 --cid 2");
String outputPath = MOUNT_DIR + "/20";
String backendPath = TEST_DIR + "/output";
// Action & Verify
- expectRemoteCommandToSucceed(
- "yes $'\\x01' | tr -d '\\n' | dd bs=1 count=10000 of=" + outputPath);
- assertEquals(getFileSizeInBytes(outputPath), 10000);
+ runOnMicrodroid("yes $'\\x01' | tr -d '\\n' | dd bs=1 count=10000 of=" + outputPath);
+ assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 10000);
expectBackingFileConsistency(
outputPath,
backendPath,
"684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
- resizeFile(outputPath, 15000);
- assertEquals(getFileSizeInBytes(outputPath), 15000);
+ resizeFileOnMicrodroid(outputPath, 15000);
+ assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 15000);
expectBackingFileConsistency(
outputPath,
backendPath,
"567c89f62586e0d33369157afdfe99a2fa36cdffb01e91dcdc0b7355262d610d");
- resizeFile(outputPath, 5000);
- assertEquals(getFileSizeInBytes(outputPath), 5000);
+ resizeFileOnMicrodroid(outputPath, 5000);
+ assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 5000);
expectBackingFileConsistency(
outputPath,
backendPath,
@@ -252,30 +246,16 @@
private void expectBackingFileConsistency(
String authFsPath, String backendPath, String expectedHash)
throws DeviceNotAvailableException {
- String hashOnAuthFs = computeFileHashInGuest(authFsPath);
+ String hashOnAuthFs = computeFileHashOnMicrodroid(authFsPath);
assertEquals("File hash is different to expectation", expectedHash, hashOnAuthFs);
- String hashOfBackingFile = computeFileHash(backendPath);
+ String hashOfBackingFile = computeFileHashOnAndroid(backendPath);
assertEquals(
"Inconsistent file hash on the backend storage", hashOnAuthFs, hashOfBackingFile);
}
- // TODO(b/178874539): This does not really run in the guest VM. Send the shell command to the
- // guest VM when authfs works across VM boundary.
- private String computeFileHashInGuest(String path) throws DeviceNotAvailableException {
- return computeFileHash(path);
- }
-
- private boolean copyFileInGuest(String src, String dest) throws DeviceNotAvailableException {
- // TODO(b/182576497): cp returns error because close(2) returns ENOSYS in the current authfs
- // implementation. We should probably fix that since programs can expect close(2) return 0.
- String cmd = "cat " + src + " > " + dest;
- CommandResult result = mDevice.executeShellV2Command(cmd);
- return result.getStatus() == CommandStatus.SUCCESS;
- }
-
- private String computeFileHash(String path) throws DeviceNotAvailableException {
- String result = expectRemoteCommandToSucceed("sha256sum " + path);
+ private String computeFileHashOnMicrodroid(String path) {
+ String result = runOnMicrodroid("sha256sum " + path);
String[] tokens = result.split("\\s");
if (tokens.length > 0) {
return tokens[0];
@@ -285,18 +265,46 @@
}
}
- private void resizeFile(String path, long size) throws DeviceNotAvailableException {
- expectRemoteCommandToSucceed("truncate -c -s " + size + " " + path);
+ private boolean copyFileOnMicrodroid(String src, String dest)
+ throws DeviceNotAvailableException {
+ // TODO(b/182576497): cp returns error because close(2) returns ENOSYS in the current authfs
+ // implementation. We should probably fix that since programs can expect close(2) return 0.
+ String cmd = "cat " + src + " > " + dest;
+ return tryRunOnMicrodroid(cmd) != null;
}
- private long getFileSizeInBytes(String path) throws DeviceNotAvailableException {
- return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + path));
- }
-
- private void throwDowncastedException(Exception e) throws DeviceNotAvailableException {
- if (e instanceof DeviceNotAvailableException) {
- throw (DeviceNotAvailableException) e;
+ private String computeFileHashOnAndroid(String path) throws DeviceNotAvailableException {
+ String result = runOnAndroid("sha256sum " + path);
+ String[] tokens = result.split("\\s");
+ if (tokens.length > 0) {
+ return tokens[0];
} else {
+ CLog.e("Unrecognized output by sha256sum: " + result);
+ return "";
+ }
+ }
+
+ private void resizeFileOnMicrodroid(String path, long size) {
+ runOnMicrodroid("truncate -c -s " + size + " " + path);
+ }
+
+ private long getFileSizeInBytesOnMicrodroid(String path) {
+ return Long.parseLong(runOnMicrodroid("stat -c '%s' " + path));
+ }
+
+ private void runAuthFsOnMicrodroid(String flags) {
+ String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
+
+ mThreadPool.submit(
+ () -> {
+ CLog.i("Starting authfs");
+ CommandResult result = runOnMicrodroidForResult(cmd);
+ CLog.w("authfs has stopped: " + result);
+ });
+ try {
+ PollingCheck.waitFor(
+ AUTHFS_INIT_TIMEOUT_MS, () -> isMicrodroidDirectoryOnFuse(MOUNT_DIR));
+ } catch (Exception e) {
// Convert the broad Exception into an unchecked exception to avoid polluting all other
// methods. waitFor throws Exception because the callback, Callable#call(), has a
// signature to throw an Exception.
@@ -304,56 +312,25 @@
}
}
- private void runAuthFsInBackground(String flags) throws DeviceNotAvailableException {
- String cmd = "cd " + TEST_DIR + " && " + AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
-
- mThreadPool.submit(() -> {
- try {
- CLog.i("Starting authfs");
- expectRemoteCommandToSucceed(cmd);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Error running authfs", e);
- throw new RuntimeException(e);
- }
- });
- try {
- PollingCheck.waitFor(AUTHFS_INIT_TIMEOUT_MS, () -> isRemoteDirectoryOnFuse(MOUNT_DIR));
- } catch (Exception e) {
- throwDowncastedException(e);
- }
- }
-
- private void runFdServerInBackground(String execParamsForOpeningFds, String flags)
+ private void runFdServerOnAndroid(String execParamsForOpeningFds, String flags)
throws DeviceNotAvailableException {
String cmd = "cd " + TEST_DIR + " && exec " + execParamsForOpeningFds + " " + FD_SERVER_BIN
+ " " + flags;
- mThreadPool.submit(() -> {
- try {
- CLog.i("Starting fd_server");
- expectRemoteCommandToSucceed(cmd);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Error running fd_server", e);
- throw new RuntimeException(e);
- }
- });
+ mThreadPool.submit(
+ () -> {
+ try {
+ CLog.i("Starting fd_server");
+ CommandResult result = getDevice().executeShellV2Command(cmd);
+ CLog.w("fd_server has stopped: " + result);
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Error running fd_server", e);
+ throw new RuntimeException(e);
+ }
+ });
}
- private boolean isRemoteDirectoryOnFuse(String path) throws DeviceNotAvailableException {
- String fs_type = expectRemoteCommandToSucceed("stat -f -c '%t' " + path);
+ private boolean isMicrodroidDirectoryOnFuse(String path) {
+ String fs_type = tryRunOnMicrodroid("stat -f -c '%t' " + path);
return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
}
-
- private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
- CommandResult result = mDevice.executeShellV2Command(cmd);
- assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
- result.getStatus());
- CLog.d("Stdout: " + result.getStdout());
- return result.getStdout().trim();
- }
-
- private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException {
- CommandResult result = mDevice.executeShellV2Command(cmd);
- assertNotEquals("Unexpected success from `" + cmd + "`: " + result.getStdout(),
- result.getStatus(), CommandStatus.SUCCESS);
- }
}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 851aa30..6d43760 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -23,12 +23,15 @@
import static org.junit.Assume.assumeThat;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+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;
import com.android.tradefed.util.RunUtil;
import java.io.File;
+import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -45,7 +48,9 @@
// Set the maximum timeout value big enough.
private static final long MICRODROID_BOOT_TIMEOUT_MINUTES = 5;
- public void prepareVirtualizationTestSetup() throws Exception {
+ private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
+
+ public void prepareVirtualizationTestSetup() throws DeviceNotAvailableException {
// kill stale crosvm processes
tryRunOnAndroid("killall", "crosvm");
@@ -57,7 +62,7 @@
tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
}
- public void cleanUpVirtualizationTestSetup() throws Exception {
+ public void cleanUpVirtualizationTestSetup() throws DeviceNotAvailableException {
// disconnect from microdroid
tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
@@ -67,7 +72,7 @@
tryRunOnAndroid("stop", "virtualizationservice");
}
- public void testIfDeviceIsCapable() throws Exception {
+ public void testIfDeviceIsCapable() throws DeviceNotAvailableException {
// Checks the preconditions to run microdroid. If the condition is not satisfied
// don't run the test (instead of failing)
skipIfFail("ls /dev/kvm");
@@ -96,7 +101,7 @@
}
// Run a shell command on Android. the default timeout is 2 min by tradefed
- private String runOnAndroid(String... cmd) throws Exception {
+ public String runOnAndroid(String... cmd) throws DeviceNotAvailableException {
CommandResult result = getDevice().executeShellV2Command(join(cmd));
if (result.getStatus() != CommandStatus.SUCCESS) {
fail(join(cmd) + " has failed: " + result);
@@ -104,13 +109,19 @@
return result.getStdout().trim();
}
- // Same as runOnAndroid, but failure is not an error
- private String tryRunOnAndroid(String... cmd) throws Exception {
+ // Same as runOnAndroid, but returns null on error.
+ public String tryRunOnAndroid(String... cmd) throws DeviceNotAvailableException {
CommandResult result = getDevice().executeShellV2Command(join(cmd));
- return result.getStdout().trim();
+ if (result.getStatus() == CommandStatus.SUCCESS) {
+ return result.getStdout().trim();
+ } else {
+ CLog.d(join(cmd) + " has failed (but ok): " + result);
+ return null;
+ }
}
- private String runOnAndroidWithTimeout(long timeoutMillis, String... cmd) throws Exception {
+ private String runOnAndroidWithTimeout(long timeoutMillis, String... cmd)
+ throws DeviceNotAvailableException {
CommandResult result =
getDevice()
.executeShellV2Command(
@@ -125,26 +136,46 @@
// Run a shell command on Microdroid
public String runOnMicrodroid(String... cmd) {
- final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
- CommandResult result =
- RunUtil.getDefault()
- .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ CommandResult result = runOnMicrodroidForResult(cmd);
if (result.getStatus() != CommandStatus.SUCCESS) {
fail(join(cmd) + " has failed: " + result);
}
return result.getStdout().trim();
}
+ // Same as runOnMicrodroid, but returns null on error.
+ public String tryRunOnMicrodroid(String... cmd) {
+ CommandResult result = runOnMicrodroidForResult(cmd);
+ if (result.getStatus() == CommandStatus.SUCCESS) {
+ return result.getStdout().trim();
+ } else {
+ CLog.d(join(cmd) + " has failed (but ok): " + result);
+ return null;
+ }
+ }
+
+ public CommandResult runOnMicrodroidForResult(String... cmd) {
+ final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+ return RunUtil.getDefault()
+ .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ }
+
private String join(String... strs) {
return String.join(" ", Arrays.asList(strs));
}
- public File findTestFile(String name) throws Exception {
- return (new CompatibilityBuildHelper(getBuild())).getTestFile(name);
+ public File findTestFile(String name) {
+ try {
+ return (new CompatibilityBuildHelper(getBuild())).getTestFile(name);
+ } catch (FileNotFoundException e) {
+ fail("Missing test file: " + name);
+ return null;
+ }
}
public String startMicrodroid(
- String apkName, String packageName, String configPath, boolean debug) throws Exception {
+ String apkName, String packageName, String configPath, boolean debug)
+ throws DeviceNotAvailableException {
// Install APK
File apkFile = findTestFile(apkName);
getDevice().installPackage(apkFile, /* reinstall */ true);
@@ -202,16 +233,38 @@
return matcher.group(1);
}
- public void shutdownMicrodroid(String cid) throws Exception {
+ public void shutdownMicrodroid(String cid) throws DeviceNotAvailableException {
// Shutdown microdroid
runOnAndroid(VIRT_APEX + "bin/vm", "stop", cid);
+
+ // TODO(192660485): Figure out why shutting down the VM disconnects adb on cuttlefish
+ // temporarily. Without this wait, the rest of `runOnAndroid/skipIfFail` fails due to the
+ // connection loss, and results in assumption error exception for the rest of the tests.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public void rootMicrodroid() throws DeviceNotAvailableException {
+ runOnHost("adb", "-s", MICRODROID_SERIAL, "root");
+
+ // TODO(192660959): Figure out the root cause and remove the sleep. For unknown reason,
+ // even though `adb root` actually wait-for-disconnect then wait-for-device, the next
+ // `adb -s $MICRODROID_SERIAL shell ...` often fails with "adb: device offline".
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
}
// Establish an adb connection to microdroid by letting Android forward the connection to
// microdroid. Wait until the connection is established and microdroid is booted.
- public void adbConnectToMicrodroid(String cid, long timeoutMinutes) throws Exception {
+ public void adbConnectToMicrodroid(String cid) throws DeviceNotAvailableException {
long start = System.currentTimeMillis();
- long timeoutMillis = timeoutMinutes * 60 * 1000;
+ long timeoutMillis = MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000;
long elapsed = 0;
final String serial = getDevice().getSerialNumber();
@@ -249,7 +302,7 @@
assertThat(runOnMicrodroid("getprop", "ro.hardware"), is("microdroid"));
}
- private void skipIfFail(String command) throws Exception {
+ private void skipIfFail(String command) throws DeviceNotAvailableException {
CommandResult result = getDevice().executeShellV2Command(command);
assumeThat(result.getStatus(), is(CommandStatus.SUCCESS));
}
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 0774503..02fb7e5 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -29,7 +29,6 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class MicrodroidTestCase extends VirtualizationTestCaseBase {
- private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
@@ -37,7 +36,7 @@
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
final String cid = startMicrodroid(APK_NAME, PACKAGE_NAME, configPath, /* debug */ false);
- adbConnectToMicrodroid(cid, MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES);
+ adbConnectToMicrodroid(cid);
// Test writing to /data partition
runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
@@ -82,7 +81,7 @@
final String configPath = "assets/vm_config.json"; // path inside the APK
final boolean debug = true;
final String cid = startMicrodroid(APK_NAME, PACKAGE_NAME, configPath, debug);
- adbConnectToMicrodroid(cid, MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES);
+ adbConnectToMicrodroid(cid);
assertThat(runOnMicrodroid("getenforce"), is("Permissive"));