Java framework for host-side virtualization tests

Add base class VirtTestCase for host-side Java virtualization tests.
It provides common methods for pushing necessary files to the device and
spawning a VM using CrosVM.

Test: VirtualizationHostTestCases
Change-Id: Ieeb129b7af772f8ab70205819766c946ae70ed11
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index d8c9871..0c894ac 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -17,6 +17,34 @@
 kernel_target_stem = ":kernel_prebuilts-" + kernel_version
 vendor_ramdisk_target_stem = ":cf_prebuilts_initramfs-" + kernel_version
 
+// JAR containing all virtualization host-side tests.
+java_test_host {
+    name: "VirtualizationHostTestCases",
+    srcs: ["java/**/*.java"],
+    test_suites: ["device-tests"],
+    libs: ["tradefed"],
+    data: [
+        ":virt_hostside_tests_kernel",
+        ":virt_hostside_tests_initramfs-arm64",
+        ":virt_hostside_tests_initramfs-x86_64",
+    ],
+}
+
+// Give kernel images unique file names.
+genrule {
+    name: "virt_hostside_tests_kernel",
+    srcs: [
+        kernel_target_stem + "-arm64",
+        kernel_target_stem + "-x86_64",
+    ],
+    out: [
+        "virt_hostside_tests_kernel-arm64",
+        "virt_hostside_tests_kernel-x86_64",
+    ],
+    tool_files: ["scripts/place_files.sh"],
+    cmd: "$(location scripts/place_files.sh) $(in) -- $(out)",
+}
+
 // Ramdisk containing /init and test binaries/resources needed inside guest.
 genrule {
     name: "virt_hostside_tests_initramfs_base",
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
new file mode 100644
index 0000000..ab77b45
--- /dev/null
+++ b/tests/hostside/AndroidTest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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="Config for Virtualization host tests">
+
+    <!-- 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" />
+        <!-- Kernel has KVM enabled. -->
+        <option name="run-command" value="ls /dev/kvm" />
+        <!-- Kernel has vhost-vsock enabled. -->
+        <option name="run-command" value="ls /dev/vhost-vsock" />
+        <!-- CrosVM is installed. -->
+        <option name="run-command" value="which crosvm" />
+    </target_preparer>
+
+    <!-- Push test binaries to the device. -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="abort-on-push-failure" value="true" />
+        <option name="push-file" key="virt_hostside_tests_kernel-arm64" value="/data/local/tmp/virt-test/arm64/kernel" />
+        <option name="push-file" key="virt_hostside_tests_initramfs-arm64" value="/data/local/tmp/virt-test/arm64/initramfs" />
+        <option name="push-file" key="virt_hostside_tests_kernel-x86_64" value="/data/local/tmp/virt-test/x86_64/kernel" />
+        <option name="push-file" key="virt_hostside_tests_initramfs-x86_64" value="/data/local/tmp/virt-test/x86_64/initramfs" />
+    </target_preparer>
+
+    <!-- Root currently needed to run CrosVM.
+         TODO: Give sufficient permissions to the adb shell user (b/171240450). -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="VirtualizationHostTestCases.jar" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/hostside/java/android/virt/test/VirtTestCase.java b/tests/hostside/java/android/virt/test/VirtTestCase.java
new file mode 100644
index 0000000..7ba6409
--- /dev/null
+++ b/tests/hostside/java/android/virt/test/VirtTestCase.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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 android.virt.test;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+
+import org.junit.Before;
+
+import java.util.ArrayList;
+
+public abstract class VirtTestCase extends DeviceTestCase implements IAbiReceiver {
+
+    private static final String DEVICE_DIR = "/data/local/tmp/virt-test";
+
+    private static final int CID_RESERVED = 2;
+
+    private IAbi mAbi;
+
+    @Before
+    public void setUp() throws Exception {
+        getDevice().waitForDeviceAvailable();
+    }
+
+    private String getAbiName() {
+        String name = mAbi.getName();
+        if ("arm64-v8a".equals(name)) {
+            name = "arm64";
+        }
+        return name;
+    }
+
+    protected String getDevicePathForTestBinary(String targetName) throws Exception {
+        String path = String.format("%s/%s/%s", DEVICE_DIR, getAbiName(), targetName);
+        if (!getDevice().doesFileExist(path)) {
+            throw new IllegalArgumentException(String.format(
+                    "Binary for target %s not found on device at \"%s\"", targetName, path));
+        }
+        return path;
+    }
+
+    protected static String createCommand(String prog, Object... args) {
+        ArrayList<String> strings = new ArrayList<>();
+        strings.add(prog);
+        for (Object arg : args) {
+            strings.add(arg.toString());
+        }
+        for (String str : strings) {
+            if (str.indexOf(' ') != -1) {
+                throw new IllegalArgumentException("TODO: implement quotes around arguments");
+            } else if (str.indexOf('\'') != -1) {
+                throw new IllegalArgumentException("TODO: implement escaping arguments");
+            }
+        }
+        return String.join(" ", strings);
+    }
+
+    protected String getVmCommand(String guestCmd, Integer cid) throws Exception {
+        ArrayList<String> cmd = new ArrayList<>();
+
+        cmd.add("crosvm");
+        cmd.add("run");
+
+        cmd.add("--disable-sandbox");
+
+        if (cid != null) {
+            if (cid > CID_RESERVED) {
+                cmd.add("--cid");
+                cmd.add(cid.toString());
+            } else {
+                throw new IllegalArgumentException("Invalid CID " + cid);
+            }
+        }
+
+        cmd.add("--initrd");
+        cmd.add(getDevicePathForTestBinary("initramfs"));
+
+        cmd.add("--params");
+        cmd.add(String.format("'%s'", guestCmd));
+
+        cmd.add(getDevicePathForTestBinary("kernel"));
+
+        return String.join(" ", cmd);
+    }
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+}