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