1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.microdroid.test.host; 18 19 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 20 21 import static com.google.common.truth.Truth.assertWithMessage; 22 23 import static org.junit.Assume.assumeFalse; 24 import static org.junit.Assume.assumeTrue; 25 26 import com.android.microdroid.test.common.DeviceProperties; 27 import com.android.microdroid.test.common.MetricsProcessor; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.device.TestDevice; 31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 32 import com.android.tradefed.util.CommandResult; 33 import com.android.tradefed.util.CommandStatus; 34 import com.android.tradefed.util.RunUtil; 35 import com.android.tradefed.util.SearchArtifactUtil; 36 37 import org.json.JSONArray; 38 import org.json.JSONObject; 39 40 import java.io.File; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.Map; 45 46 public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test { 47 protected static final String TEST_ROOT = "/data/local/tmp/virt/"; 48 protected static final String TRADEFED_TEST_ROOT = "/data/local/tmp/virt/tradefed/"; 49 protected static final String LOG_PATH = TEST_ROOT + "log.txt"; 50 protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt"; 51 protected static final String TRADEFED_CONSOLE_PATH = TRADEFED_TEST_ROOT + "console.txt"; 52 protected static final String TRADEFED_LOG_PATH = TRADEFED_TEST_ROOT + "log.txt"; 53 private static final int TEST_VM_ADB_PORT = 8000; 54 private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT; 55 private static final String INSTANCE_IMG = "instance.img"; 56 protected static final String VIRT_APEX = "/apex/com.android.virt/"; 57 protected static final String SECRETKEEPER_AIDL = 58 "android.hardware.security.secretkeeper.ISecretkeeper/default"; 59 60 private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5; 61 protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000; 62 private static final long MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS = 500; 63 protected static final int MICRODROID_ADB_CONNECT_MAX_ATTEMPTS = 64 (int) 65 (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES 66 * 60 67 * 1000 68 / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS); 69 70 // We use a map here because the parameterizer `DeviceParameterizedRunner` doesn't support "-" 71 // in test names. 72 // The key of the map is the name of the parameter while the value is the actual OS variant. 73 protected static final Map<String, String> SUPPORTED_OSES = 74 Map.ofEntries( 75 Map.entry("microdroid", "microdroid"), 76 Map.entry("microdroid_16k", "microdroid_16k"), 77 Map.entry("android15_66", "microdroid_gki-android15-6.6")); 78 79 /* Keep this sync with AssignableDevice.aidl */ 80 public static final class AssignableDevice { 81 public final String node; 82 public final String dtbo_label; 83 AssignableDevice(String node, String dtbo_label)84 public AssignableDevice(String node, String dtbo_label) { 85 this.node = node; 86 this.dtbo_label = dtbo_label; 87 } 88 } 89 prepareVirtualizationTestSetup(ITestDevice androidDevice)90 public static void prepareVirtualizationTestSetup(ITestDevice androidDevice) 91 throws DeviceNotAvailableException { 92 CommandRunner android = new CommandRunner(androidDevice); 93 94 // kill stale crosvm processes 95 android.tryRun("killall", "crosvm"); 96 97 // disconnect from microdroid 98 tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL); 99 100 // remove any leftover files under test root 101 android.tryRun("rm", "-rf", TEST_ROOT + "*"); 102 103 android.tryRun("mkdir " + TEST_ROOT); 104 } 105 cleanUpVirtualizationTestSetup(ITestDevice androidDevice)106 public static void cleanUpVirtualizationTestSetup(ITestDevice androidDevice) 107 throws DeviceNotAvailableException { 108 CommandRunner android = new CommandRunner(androidDevice); 109 110 // disconnect from microdroid 111 tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL); 112 113 // kill stale VMs and directories 114 android.tryRun("killall", "crosvm"); 115 android.tryRun("stop", "virtualizationservice"); 116 android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*"); 117 } 118 isUserBuild()119 public boolean isUserBuild() { 120 return DeviceProperties.create(getDevice()::getProperty).isUserBuild(); 121 } 122 isCuttlefish()123 protected boolean isCuttlefish() { 124 return DeviceProperties.create(getDevice()::getProperty).isCuttlefish(); 125 } 126 isHwasan()127 protected boolean isHwasan() { 128 return DeviceProperties.create(getDevice()::getProperty).isHwasan(); 129 } 130 getMetricPrefix()131 protected String getMetricPrefix() { 132 return MetricsProcessor.getMetricPrefix( 133 DeviceProperties.create(getDevice()::getProperty).getMetricsTag()); 134 } 135 assumeDeviceIsCapable(ITestDevice androidDevice)136 public static void assumeDeviceIsCapable(ITestDevice androidDevice) throws Exception { 137 assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice); 138 TestDevice testDevice = (TestDevice) androidDevice; 139 assumeTrue( 140 "Requires VM support", 141 testDevice.hasFeature("android.software.virtualization_framework")); 142 assumeTrue("Requires VM support", testDevice.supportsMicrodroid()); 143 144 CommandRunner android = new CommandRunner(androidDevice); 145 long vendorApiLevel = androidDevice.getIntProperty("ro.board.api_level", 0); 146 boolean isGsi = 147 android.runForResult("[ -e /system/system_ext/etc/init/init.gsi.rc ]").getStatus() 148 == CommandStatus.SUCCESS; 149 assumeFalse( 150 "GSI with vendor API level < 202404 may not support AVF", 151 isGsi && vendorApiLevel < 202404); 152 } 153 archiveLogThenDelete( TestLogData logs, ITestDevice device, String remotePath, String localName)154 public static void archiveLogThenDelete( 155 TestLogData logs, ITestDevice device, String remotePath, String localName) 156 throws DeviceNotAvailableException { 157 LogArchiver.archiveLogThenDelete(logs, device, remotePath, localName); 158 } 159 setPropertyOrThrow(ITestDevice device, String propertyName, String value)160 public static void setPropertyOrThrow(ITestDevice device, String propertyName, String value) 161 throws DeviceNotAvailableException { 162 if (!device.setProperty(propertyName, value)) { 163 throw new RuntimeException("Failed to set sysprop " + propertyName + " to " + value); 164 } 165 } 166 167 // Run an arbitrary command in the host side and returns the result. 168 // Note failure is not an error. tryRunOnHost(String... cmd)169 public static String tryRunOnHost(String... cmd) { 170 final long timeout = 10000; 171 CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd); 172 return result.getStdout().trim(); 173 } 174 join(String... strs)175 private static String join(String... strs) { 176 return String.join(" ", Arrays.asList(strs)); 177 } 178 findTestFile(String name)179 public File findTestFile(String name) { 180 String moduleName = getInvocationContext().getConfigurationDescriptor().getModuleName(); 181 File testFile = SearchArtifactUtil.searchFile(name, false); 182 if (testFile == null) { 183 throw new AssertionError( 184 "Failed to find test file " + name + " for module " + moduleName); 185 } 186 return testFile; 187 } 188 getPathForPackage(String packageName)189 public String getPathForPackage(String packageName) throws DeviceNotAvailableException { 190 return getPathForPackage(getDevice(), packageName); 191 } 192 193 // Get the path to the installed apk. Note that 194 // getDevice().getAppPackageInfo(...).getCodePath() doesn't work due to the incorrect 195 // parsing of the "=" character. (b/190975227). So we use the `pm path` command directly. getPathForPackage(ITestDevice device, String packageName)196 private static String getPathForPackage(ITestDevice device, String packageName) 197 throws DeviceNotAvailableException { 198 CommandRunner android = new CommandRunner(device); 199 String pathLine = android.run("pm", "path", packageName); 200 assertWithMessage("Package " + packageName + " not found") 201 .that(pathLine) 202 .startsWith("package:"); 203 return pathLine.substring("package:".length()); 204 } 205 parseFieldFromVmInfo(String header)206 public String parseFieldFromVmInfo(String header) throws Exception { 207 CommandRunner android = new CommandRunner(getDevice()); 208 String result = android.run("/apex/com.android.virt/bin/vm", "info"); 209 for (String line : result.split("\n")) { 210 if (!line.startsWith(header)) continue; 211 212 return line.substring(header.length()); 213 } 214 return ""; 215 } 216 parseStringArrayFieldsFromVmInfo(String header)217 public List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception { 218 String field = parseFieldFromVmInfo(header); 219 220 List<String> ret = new ArrayList<>(); 221 if (!field.isEmpty()) { 222 JSONArray jsonArray = new JSONArray(field); 223 for (int i = 0; i < jsonArray.length(); i++) { 224 ret.add(jsonArray.getString(i)); 225 } 226 } 227 return ret; 228 } 229 isFeatureEnabled(String feature)230 public boolean isFeatureEnabled(String feature) throws Exception { 231 CommandRunner android = new CommandRunner(getDevice()); 232 String result = android.run(VIRT_APEX + "bin/vm", "check-feature-enabled", feature); 233 return result.contains("enabled"); 234 } 235 getAssignableDevices()236 public List<AssignableDevice> getAssignableDevices() throws Exception { 237 String field = parseFieldFromVmInfo("Assignable devices: "); 238 239 List<AssignableDevice> ret = new ArrayList<>(); 240 if (!field.isEmpty()) { 241 JSONArray jsonArray = new JSONArray(field); 242 for (int i = 0; i < jsonArray.length(); i++) { 243 JSONObject jsonObject = jsonArray.getJSONObject(i); 244 ret.add( 245 new AssignableDevice( 246 jsonObject.getString("node"), jsonObject.getString("dtbo_label"))); 247 } 248 } 249 return ret; 250 } 251 isUpdatableVmSupported()252 public boolean isUpdatableVmSupported() throws DeviceNotAvailableException { 253 // Updatable VMs are possible iff device supports Secretkeeper. 254 CommandRunner android = new CommandRunner(getDevice()); 255 CommandResult result = android.runForResult("service check", SECRETKEEPER_AIDL); 256 assertWithMessage("Failed to run service check. Result= " + result) 257 .that(result.getStatus() == CommandStatus.SUCCESS && result.getExitCode() == 0) 258 .isTrue(); 259 boolean is_sk_supported = !result.getStdout().trim().contains("not found"); 260 return is_sk_supported; 261 } 262 getSupportedOSList()263 public List<String> getSupportedOSList() throws Exception { 264 return parseStringArrayFieldsFromVmInfo("Available OS list: "); 265 } 266 isPkvmHypervisor()267 protected boolean isPkvmHypervisor() throws DeviceNotAvailableException { 268 return "kvm.arm-protected".equals(getDevice().getProperty("ro.boot.hypervisor.version")); 269 } 270 } 271