| /* |
| * 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.build.IBuildInfo; |
| import com.android.tradefed.testtype.DeviceTestCase; |
| import com.android.tradefed.testtype.IAbi; |
| import com.android.tradefed.testtype.IAbiReceiver; |
| import com.android.tradefed.testtype.IBuildReceiver; |
| import com.android.tradefed.log.LogUtil.CLog; |
| |
| import org.apache.commons.compress.compressors.CompressorStreamFactory; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| |
| public abstract class VirtHostTestCase extends DeviceTestCase |
| implements IBuildReceiver, IAbiReceiver { |
| |
| private static final String DEVICE_TMP_DIR = "/data/local/tmp/"; |
| private static final String KERNEL_DEVICE_PATH = DEVICE_TMP_DIR + "/virt-host-test-kernel"; |
| private static final String RAMDISK_DEVICE_PATH = DEVICE_TMP_DIR + "/virt-host-test-initramfs"; |
| private static final String HOST_BINARY_DEVICE_PATH = DEVICE_TMP_DIR + "/virt-host-test-host"; |
| |
| private static final int EXIT_SUCCESS = 0; |
| private static final int EXIT_FAILURE = 1; |
| |
| private static final String CMDLINE_TEST_NAME_PARAM = "virt_test_name"; |
| |
| private static final int CID_RESERVED = 2; |
| |
| private IBuildInfo mBuildInfo = null; |
| private IAbi mAbi = null; |
| |
| /** |
| * Waits for device to be online, marks the most recent boottime of the device |
| */ |
| @Before |
| public void setUp() throws Exception { |
| getDevice().waitForDeviceAvailable(); |
| } |
| |
| protected boolean enableRoot() throws Exception { |
| return getDevice().enableAdbRoot(); |
| } |
| |
| protected boolean disableRoot() throws Exception { |
| return getDevice().disableAdbRoot(); |
| } |
| |
| protected String runCommand(String cmd) throws Exception { |
| return getDevice().executeShellCommand(cmd); |
| } |
| |
| protected int runCommandGetExitCode(String cmd) throws Exception { |
| String output = runCommand("(" + cmd + ") > /dev/null 2>&1; echo $?").trim(); |
| |
| try { |
| return Integer.parseInt(output); |
| } catch (NumberFormatException e) { |
| throw new IllegalArgumentException(String.format( |
| "Could not get the exit status (%s) for '%s'.", output, cmd)); |
| } |
| } |
| |
| private void extractResource(String resFilePath, File file) throws Exception { |
| try (InputStream in = this.getClass().getResourceAsStream(resFilePath); |
| OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { |
| if (in == null) { |
| throw new IllegalArgumentException("Resource not found: " + resFilePath); |
| } |
| byte[] buf = new byte[65536]; |
| int chunkSize; |
| while ((chunkSize = in.read(buf)) != -1) { |
| out.write(buf, 0, chunkSize); |
| } |
| } |
| } |
| |
| private void pushResource(String resFilePath, String deviceFilePath, boolean decompress) |
| throws Exception { |
| File resFile = File.createTempFile("VirtHostTestResource", ""); |
| try { |
| extractResource(resFilePath, resFile); |
| getDevice().pushFile(resFile, deviceFilePath); |
| } finally { |
| resFile.delete(); |
| } |
| } |
| |
| private void pushResource(String resFilePath, String deviceFilePath) throws Exception { |
| pushResource(resFilePath, deviceFilePath, /* decompress */ false); |
| } |
| |
| private boolean deleteResource(String deviceFilePath) throws Exception { |
| return runCommandGetExitCode(String.format("rm %s", deviceFilePath)) == EXIT_SUCCESS; |
| } |
| |
| private boolean makeExecutable(String deviceFilePath) throws Exception { |
| return runCommandGetExitCode(String.format("chmod +x %s", deviceFilePath)) == EXIT_SUCCESS; |
| } |
| |
| protected boolean isVirtEnabled() throws Exception { |
| return (runCommandGetExitCode("ls /dev/kvm") == EXIT_SUCCESS) && |
| (runCommandGetExitCode("which crosvm") == EXIT_SUCCESS); |
| } |
| |
| private String getKernelResourcePath() { |
| return String.format("/kernel_%s", mAbi.getName()); |
| } |
| |
| private String getRamdiskResourcePath() { |
| return String.format("/initramfs_%s.cpio", mAbi.getName()); |
| } |
| |
| private String getHostBinaryResourcePath() { |
| return String.format("/host_binary"); |
| } |
| |
| protected void prepareGuestVM() throws Exception { |
| if (!isVirtEnabled()) { |
| throw new IllegalStateException("Virtualization is not enabled on the target device"); |
| } |
| |
| if (!enableRoot()) { |
| throw new IllegalStateException("Cannot enable root on the target device"); |
| } |
| |
| pushResource(getKernelResourcePath(), KERNEL_DEVICE_PATH); |
| pushResource(getRamdiskResourcePath(), RAMDISK_DEVICE_PATH); |
| pushResource(getHostBinaryResourcePath(), HOST_BINARY_DEVICE_PATH); |
| |
| if (!makeExecutable(HOST_BINARY_DEVICE_PATH)) { |
| throw new IllegalStateException("Could not make host binary executable"); |
| } |
| } |
| |
| protected void finishGuestVM() throws Exception { |
| deleteResource(KERNEL_DEVICE_PATH); |
| deleteResource(RAMDISK_DEVICE_PATH); |
| deleteResource(HOST_BINARY_DEVICE_PATH); |
| disableRoot(); |
| } |
| |
| protected void runGuestVM(String testName, Integer cid) throws Exception { |
| ArrayList<String> cmd = new ArrayList<>(); |
| String params = String.format("%s=%s", CMDLINE_TEST_NAME_PARAM, testName); |
| |
| 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"); |
| } |
| } |
| |
| cmd.add("--params"); |
| cmd.add("\"" + params.replace("\"", "\\\"") + "\""); |
| |
| cmd.add("--initrd"); |
| cmd.add(RAMDISK_DEVICE_PATH); |
| |
| cmd.add(KERNEL_DEVICE_PATH); |
| |
| runCommand(String.join(" ", cmd)); |
| } |
| |
| protected boolean runHostBinary(String testName) throws Exception { |
| ArrayList<String> cmd = new ArrayList<>(); |
| |
| cmd.add(HOST_BINARY_DEVICE_PATH); |
| cmd.add(testName); |
| |
| return runCommandGetExitCode(String.join(" ", cmd)) == EXIT_SUCCESS; |
| } |
| |
| @Override |
| public void setBuild(IBuildInfo buildInfo) { |
| // Get the build, this is used to access the APK. |
| mBuildInfo = buildInfo; |
| } |
| |
| @Override |
| public void setAbi(IAbi abi) { |
| mAbi = abi; |
| } |
| } |