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.compatibility.common.tradefed.build.CompatibilityBuildHelper; 27 import com.android.microdroid.test.common.DeviceProperties; 28 import com.android.microdroid.test.common.MetricsProcessor; 29 import com.android.tradefed.build.IBuildInfo; 30 import com.android.tradefed.device.DeviceNotAvailableException; 31 import com.android.tradefed.device.ITestDevice; 32 import com.android.tradefed.device.TestDevice; 33 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 34 import com.android.tradefed.util.CommandResult; 35 import com.android.tradefed.util.CommandStatus; 36 import com.android.tradefed.util.FileUtil; 37 import com.android.tradefed.util.RunUtil; 38 39 import org.json.JSONArray; 40 import org.json.JSONObject; 41 42 import java.io.File; 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Set; 50 import java.util.stream.Collectors; 51 52 public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test { 53 protected static final String TEST_ROOT = "/data/local/tmp/virt/"; 54 protected static final String TRADEFED_TEST_ROOT = "/data/local/tmp/virt/tradefed/"; 55 protected static final String LOG_PATH = TEST_ROOT + "log.txt"; 56 protected static final String CONSOLE_PATH = TEST_ROOT + "console.txt"; 57 protected static final String TRADEFED_CONSOLE_PATH = TRADEFED_TEST_ROOT + "console.txt"; 58 protected static final String TRADEFED_LOG_PATH = TRADEFED_TEST_ROOT + "log.txt"; 59 private static final int TEST_VM_ADB_PORT = 8000; 60 private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT; 61 private static final String INSTANCE_IMG = "instance.img"; 62 protected static final String VIRT_APEX = "/apex/com.android.virt/"; 63 protected static final String SECRETKEEPER_AIDL = 64 "android.hardware.security.secretkeeper.ISecretkeeper/default"; 65 66 private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5; 67 protected static final long MICRODROID_COMMAND_TIMEOUT_MILLIS = 30000; 68 private static final long MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS = 500; 69 protected static final int MICRODROID_ADB_CONNECT_MAX_ATTEMPTS = 70 (int) (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000 71 / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS); 72 73 protected static final Set<String> SUPPORTED_GKI_VERSIONS = 74 Collections.unmodifiableSet( 75 new HashSet(Arrays.asList("android14-6.1-pkvm_experimental"))); 76 77 /* Keep this sync with AssignableDevice.aidl */ 78 public static final class AssignableDevice { 79 public final String node; 80 public final String dtbo_label; 81 AssignableDevice(String node, String dtbo_label)82 public AssignableDevice(String node, String dtbo_label) { 83 this.node = node; 84 this.dtbo_label = dtbo_label; 85 } 86 } 87 prepareVirtualizationTestSetup(ITestDevice androidDevice)88 public static void prepareVirtualizationTestSetup(ITestDevice androidDevice) 89 throws DeviceNotAvailableException { 90 CommandRunner android = new CommandRunner(androidDevice); 91 92 // kill stale crosvm processes 93 android.tryRun("killall", "crosvm"); 94 95 // disconnect from microdroid 96 tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL); 97 98 // remove any leftover files under test root 99 android.tryRun("rm", "-rf", TEST_ROOT + "*"); 100 101 android.tryRun("mkdir " + TEST_ROOT); 102 } 103 cleanUpVirtualizationTestSetup(ITestDevice androidDevice)104 public static void cleanUpVirtualizationTestSetup(ITestDevice androidDevice) 105 throws DeviceNotAvailableException { 106 CommandRunner android = new CommandRunner(androidDevice); 107 108 // disconnect from microdroid 109 tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL); 110 111 // kill stale VMs and directories 112 android.tryRun("killall", "crosvm"); 113 android.tryRun("stop", "virtualizationservice"); 114 android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*"); 115 } 116 isUserBuild()117 public boolean isUserBuild() { 118 return DeviceProperties.create(getDevice()::getProperty).isUserBuild(); 119 } 120 isCuttlefish()121 protected boolean isCuttlefish() { 122 return DeviceProperties.create(getDevice()::getProperty).isCuttlefish(); 123 } 124 isHwasan()125 protected boolean isHwasan() { 126 return DeviceProperties.create(getDevice()::getProperty).isHwasan(); 127 } 128 getMetricPrefix()129 protected String getMetricPrefix() { 130 return MetricsProcessor.getMetricPrefix( 131 DeviceProperties.create(getDevice()::getProperty).getMetricsTag()); 132 } 133 assumeDeviceIsCapable(ITestDevice androidDevice)134 public static void assumeDeviceIsCapable(ITestDevice androidDevice) throws Exception { 135 assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice); 136 TestDevice testDevice = (TestDevice) androidDevice; 137 assumeTrue( 138 "Requires VM support", 139 testDevice.hasFeature("android.software.virtualization_framework")); 140 assumeTrue("Requires VM support", testDevice.supportsMicrodroid()); 141 142 CommandRunner android = new CommandRunner(androidDevice); 143 long vendorApiLevel = androidDevice.getIntProperty("ro.board.api_level", 0); 144 boolean isGsi = 145 android.runForResult("[ -e /system/system_ext/etc/init/init.gsi.rc ]").getStatus() 146 == CommandStatus.SUCCESS; 147 assumeFalse( 148 "GSI with vendor API level < 202404 may not support AVF", 149 isGsi && vendorApiLevel < 202404); 150 } 151 archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath, String localName)152 public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath, 153 String localName) throws DeviceNotAvailableException { 154 LogArchiver.archiveLogThenDelete(logs, device, remotePath, localName); 155 } 156 setPropertyOrThrow(ITestDevice device, String propertyName, String value)157 public static void setPropertyOrThrow(ITestDevice device, String propertyName, String value) 158 throws DeviceNotAvailableException { 159 if (!device.setProperty(propertyName, value)) { 160 throw new RuntimeException("Failed to set sysprop " + propertyName + " to " + value); 161 } 162 } 163 164 // Run an arbitrary command in the host side and returns the result. 165 // Note failure is not an error. tryRunOnHost(String... cmd)166 public static String tryRunOnHost(String... cmd) { 167 final long timeout = 10000; 168 CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd); 169 return result.getStdout().trim(); 170 } join(String... strs)171 private static String join(String... strs) { 172 return String.join(" ", Arrays.asList(strs)); 173 } 174 findTestFile(String name)175 public File findTestFile(String name) { 176 String moduleName = getInvocationContext().getConfigurationDescriptor().getModuleName(); 177 IBuildInfo buildInfo = getBuild(); 178 CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo); 179 180 // We're not using helper.getTestFile here because it sometimes picks a file 181 // from a different module, which may be old and/or wrong. See b/328779049. 182 try { 183 File testsDir = helper.getTestsDir().getAbsoluteFile(); 184 185 for (File subDir : FileUtil.findDirsUnder(testsDir, testsDir.getParentFile())) { 186 if (!subDir.getName().equals(moduleName)) { 187 continue; 188 } 189 File testFile = FileUtil.findFile(subDir, name); 190 if (testFile != null) { 191 return testFile; 192 } 193 } 194 } catch (IOException e) { 195 throw new AssertionError( 196 "Failed to find test file " + name + " for module " + moduleName, e); 197 } 198 throw new AssertionError("Failed to find test file " + name + " for module " + moduleName); 199 } 200 getPathForPackage(String packageName)201 public String getPathForPackage(String packageName) 202 throws DeviceNotAvailableException { 203 return getPathForPackage(getDevice(), packageName); 204 } 205 206 // Get the path to the installed apk. Note that 207 // getDevice().getAppPackageInfo(...).getCodePath() doesn't work due to the incorrect 208 // parsing of the "=" character. (b/190975227). So we use the `pm path` command directly. getPathForPackage(ITestDevice device, String packageName)209 private static String getPathForPackage(ITestDevice device, String packageName) 210 throws DeviceNotAvailableException { 211 CommandRunner android = new CommandRunner(device); 212 String pathLine = android.run("pm", "path", packageName); 213 assertWithMessage("Package " + packageName + " not found") 214 .that(pathLine).startsWith("package:"); 215 return pathLine.substring("package:".length()); 216 } 217 parseFieldFromVmInfo(String header)218 public String parseFieldFromVmInfo(String header) throws Exception { 219 CommandRunner android = new CommandRunner(getDevice()); 220 String result = android.run("/apex/com.android.virt/bin/vm", "info"); 221 for (String line : result.split("\n")) { 222 if (!line.startsWith(header)) continue; 223 224 return line.substring(header.length()); 225 } 226 return ""; 227 } 228 parseStringArrayFieldsFromVmInfo(String header)229 public List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception { 230 String field = parseFieldFromVmInfo(header); 231 232 List<String> ret = new ArrayList<>(); 233 if (!field.isEmpty()) { 234 JSONArray jsonArray = new JSONArray(field); 235 for (int i = 0; i < jsonArray.length(); i++) { 236 ret.add(jsonArray.getString(i)); 237 } 238 } 239 return ret; 240 } 241 isFeatureEnabled(String feature)242 public boolean isFeatureEnabled(String feature) throws Exception { 243 CommandRunner android = new CommandRunner(getDevice()); 244 String result = android.run(VIRT_APEX + "bin/vm", "check-feature-enabled", feature); 245 return result.contains("enabled"); 246 } 247 getAssignableDevices()248 public List<AssignableDevice> getAssignableDevices() throws Exception { 249 String field = parseFieldFromVmInfo("Assignable devices: "); 250 251 List<AssignableDevice> ret = new ArrayList<>(); 252 if (!field.isEmpty()) { 253 JSONArray jsonArray = new JSONArray(field); 254 for (int i = 0; i < jsonArray.length(); i++) { 255 JSONObject jsonObject = jsonArray.getJSONObject(i); 256 ret.add( 257 new AssignableDevice( 258 jsonObject.getString("node"), jsonObject.getString("dtbo_label"))); 259 } 260 } 261 return ret; 262 } 263 isUpdatableVmSupported()264 public boolean isUpdatableVmSupported() throws DeviceNotAvailableException { 265 // Updatable VMs are possible iff device supports Secretkeeper. 266 CommandRunner android = new CommandRunner(getDevice()); 267 CommandResult result = android.runForResult("service check", SECRETKEEPER_AIDL); 268 assertWithMessage("Failed to run service check. Result= " + result) 269 .that(result.getStatus() == CommandStatus.SUCCESS && result.getExitCode() == 0) 270 .isTrue(); 271 boolean is_sk_supported = !result.getStdout().trim().contains("not found"); 272 return is_sk_supported; 273 } 274 getSupportedOSList()275 public List<String> getSupportedOSList() throws Exception { 276 return parseStringArrayFieldsFromVmInfo("Available OS list: "); 277 } 278 getSupportedGKIVersions()279 public List<String> getSupportedGKIVersions() throws Exception { 280 return getSupportedOSList().stream() 281 .filter(os -> os.startsWith("microdroid_gki-")) 282 .map(os -> os.replaceFirst("^microdroid_gki-", "")) 283 .collect(Collectors.toList()); 284 } 285 isPkvmHypervisor()286 protected boolean isPkvmHypervisor() throws DeviceNotAvailableException { 287 return getDevice().getProperty("ro.boot.hypervisor.version").equals("kvm.arm-protected"); 288 } 289 } 290