• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.microdroid.test.device;
17 
18 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
19 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
20 import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK;
21 import static android.content.pm.PackageManager.FEATURE_WATCH;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 import static com.google.common.truth.TruthJUnit.assume;
26 
27 import static org.junit.Assume.assumeFalse;
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.app.ActivityManager;
31 import android.app.Instrumentation;
32 import android.app.UiAutomation;
33 import android.content.Context;
34 import android.os.Build;
35 import android.os.ParcelFileDescriptor;
36 import android.os.SystemProperties;
37 import android.system.Os;
38 import android.system.virtualmachine.VirtualMachine;
39 import android.system.virtualmachine.VirtualMachineCallback;
40 import android.system.virtualmachine.VirtualMachineConfig;
41 import android.system.virtualmachine.VirtualMachineException;
42 import android.system.virtualmachine.VirtualMachineManager;
43 import android.util.Log;
44 
45 import androidx.annotation.CallSuper;
46 import androidx.test.core.app.ApplicationProvider;
47 import androidx.test.platform.app.InstrumentationRegistry;
48 
49 import com.android.microdroid.test.common.DeviceProperties;
50 import com.android.microdroid.test.common.MetricsProcessor;
51 import com.android.microdroid.testservice.ITestService;
52 import com.android.virt.vm_attestation.testservice.IAttestationService;
53 import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
54 
55 import java.io.BufferedReader;
56 import java.io.ByteArrayOutputStream;
57 import java.io.File;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.InputStreamReader;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.OptionalLong;
66 import java.util.Set;
67 import java.util.concurrent.CompletableFuture;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.TimeUnit;
71 
72 public abstract class MicrodroidDeviceTestBase {
73     private static final String TAG = "MicrodroidDeviceTestBase";
74     private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP";
75 
76     protected static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
77 
getSupportedOSes()78     private static final List<String> getSupportedOSes() {
79         List<String> ret = new ArrayList<>();
80         ret.add("microdroid");
81         if (Build.VERSION.SDK_INT >= 35) {
82             ret.add("microdroid_gki-android15-6.6");
83         }
84         if (Build.VERSION.SDK_INT >= 36) {
85             ret.add("microdroid_16k");
86         }
87         return ret;
88     }
89 
90     protected static final Set<String> SUPPORTED_OSES =
91             Collections.unmodifiableSet(new HashSet<>(getSupportedOSes()));
92 
93     private static final long ONE_MEBI = 1024 * 1024;
94     private static final long MIN_MEM_ARM64 = 170 * ONE_MEBI;
95     private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
96 
isCuttlefish()97     public static boolean isCuttlefish() {
98         return getDeviceProperties().isCuttlefish();
99     }
100 
isCuttlefishArm64()101     private static boolean isCuttlefishArm64() {
102         return getDeviceProperties().isCuttlefishArm64();
103     }
104 
isGoldfish()105     public static boolean isGoldfish() {
106         return getDeviceProperties().isGoldfish();
107     }
108 
isGoldfishArm64()109     private static boolean isGoldfishArm64() {
110         return getDeviceProperties().isGoldfishArm64();
111     }
112 
isHwasan()113     public static boolean isHwasan() {
114         return getDeviceProperties().isHwasan();
115     }
116 
isUserBuild()117     public static boolean isUserBuild() {
118         return getDeviceProperties().isUserBuild();
119     }
120 
getMetricPrefix()121     public static String getMetricPrefix() {
122         return MetricsProcessor.getMetricPrefix(getDeviceProperties().getMetricsTag());
123     }
124 
getDeviceProperties()125     private static DeviceProperties getDeviceProperties() {
126         return DeviceProperties.create(SystemProperties::get);
127     }
128 
grantPermission(String permission)129     protected final void grantPermission(String permission) {
130         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
131         UiAutomation uiAutomation = instrumentation.getUiAutomation();
132         uiAutomation.grantRuntimePermission(
133                 instrumentation.getContext().getPackageName(), permission);
134     }
135 
revokePermission(String permission)136     protected final void revokePermission(String permission) {
137         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
138         UiAutomation uiAutomation = instrumentation.getUiAutomation();
139         uiAutomation.revokeRuntimePermission(
140                 instrumentation.getContext().getPackageName(), permission);
141     }
142 
setMaxPerformanceTaskProfile()143     protected final void setMaxPerformanceTaskProfile() throws IOException {
144         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
145         UiAutomation uiAutomation = instrumentation.getUiAutomation();
146         String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE;
147         String out = runInShell(TAG, uiAutomation, cmd).trim();
148         String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!";
149         if (!expect.equals(out)) {
150             throw new IOException("Could not apply max performance task profile: " + out);
151         }
152     }
153 
154     private final Context mCtx = ApplicationProvider.getApplicationContext();
155     private boolean mProtectedVm;
156     private String mOs;
157 
getContext()158     protected Context getContext() {
159         return mCtx;
160     }
161 
getVirtualMachineManager()162     public VirtualMachineManager getVirtualMachineManager() {
163         return mCtx.getSystemService(VirtualMachineManager.class);
164     }
165 
newVmConfigBuilderWithPayloadConfig(String configPath)166     public VirtualMachineConfig.Builder newVmConfigBuilderWithPayloadConfig(String configPath) {
167         VirtualMachineConfig.Builder builder = new VirtualMachineConfig.Builder(mCtx);
168         builder.setProtectedVm(mProtectedVm).setPayloadConfigPath(configPath);
169         if (Build.VERSION.SDK_INT >= 35) {
170             builder.setOs(os());
171         }
172         return builder;
173     }
174 
newVmConfigBuilderWithPayloadBinary(String binaryPath)175     public VirtualMachineConfig.Builder newVmConfigBuilderWithPayloadBinary(String binaryPath) {
176         return new VirtualMachineConfig.Builder(mCtx)
177                 .setProtectedVm(mProtectedVm)
178                 .setOs(os())
179                 .setPayloadBinaryName(binaryPath);
180     }
181 
isProtectedVm()182     protected final boolean isProtectedVm() {
183         return mProtectedVm;
184     }
185 
os()186     protected final String os() {
187         return mOs;
188     }
189 
190     /**
191      * Creates a new virtual machine, potentially removing an existing virtual machine with given
192      * name.
193      */
forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)194     public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
195             throws VirtualMachineException {
196         final VirtualMachineManager vmm = getVirtualMachineManager();
197         deleteVirtualMachineIfExists(name);
198         return vmm.create(name, config);
199     }
200 
deleteVirtualMachineIfExists(String name)201     protected void deleteVirtualMachineIfExists(String name) throws VirtualMachineException {
202         VirtualMachineManager vmm = getVirtualMachineManager();
203         boolean deleteExisting;
204         try {
205             deleteExisting = vmm.get(name) != null;
206         } catch (VirtualMachineException e) {
207             // VM exists, i.e. there are some files for it, but they could not be successfully
208             // loaded.
209             deleteExisting = true;
210         }
211         if (deleteExisting) {
212             vmm.delete(name);
213         }
214     }
215 
prepareTestSetup(boolean protectedVm, String os)216     public void prepareTestSetup(boolean protectedVm, String os) {
217         assumeFeatureVirtualizationFramework();
218 
219         mProtectedVm = protectedVm;
220         mOs = os;
221 
222         int capabilities = getVirtualMachineManager().getCapabilities();
223         if (protectedVm) {
224             assume().withMessage("Skip where protected VMs aren't supported")
225                     .that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
226                     .isNotEqualTo(0);
227             // TODO(b/376870129): remove this
228             assume().withMessage("pVMs with 16k kernel are not supported yet :(")
229                     .that(mOs)
230                     .doesNotContain("_16k");
231         } else {
232             assume().withMessage("Skip where VMs aren't supported")
233                     .that(capabilities & VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM)
234                     .isNotEqualTo(0);
235         }
236 
237         try {
238             assume().withMessage("Skip where requested OS \"" + os() + "\" isn't supported")
239                     .that(os())
240                     .isIn(getVirtualMachineManager().getSupportedOSList());
241         } catch (VirtualMachineException e) {
242             Log.e(TAG, "Error getting supported OS list", e);
243             throw new RuntimeException("Failed to get supported OS list.", e);
244         }
245     }
246 
assumeFeatureVirtualizationFramework()247     protected void assumeFeatureVirtualizationFramework() {
248         assume().withMessage("Device doesn't support AVF")
249                 .that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
250                 .isTrue();
251         int vendorApiLevel = getVendorApiLevel();
252         boolean isGsi = isGsi();
253         Log.i(TAG, "isGsi = " + isGsi + ", vendor api level = " + vendorApiLevel);
254         assume().withMessage("GSI with vendor API level < 202404 may not support AVF")
255                 .that(isGsi && vendorApiLevel < 202404)
256                 .isFalse();
257     }
258 
259     protected void assumeVsrCompliant() {
260         boolean featureCheck =
261                 mCtx.getPackageManager().hasSystemFeature(FEATURE_WATCH)
262                         || mCtx.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)
263                         || mCtx.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
264         assume().withMessage("This device is not VSR compliant").that(featureCheck).isFalse();
265     }
266 
267     protected boolean isGsi() {
268         return new File("/system/system_ext/etc/init/init.gsi.rc").exists();
269     }
270 
271     protected static int getVendorApiLevel() {
272         return SystemProperties.getInt("ro.board.api_level", 0);
273     }
274 
275     /**
276      * @return The vendor API level that the device as a whole must conform to, this value should be
277      *     available on both GRF and non-GRF devices.
278      */
279     protected static int getFirstVendorApiLevel() {
280         return SystemProperties.getInt("ro.vendor.api_level", -1);
281     }
282 
283     protected void assumeSupportedDevice() {
284         assume().withMessage("Skip on 5.4 kernel. b/218303240")
285                 .that(KERNEL_VERSION)
286                 .isNotEqualTo("5.4");
287 
288         // Cuttlefish/Goldfish on Arm 64 doesn't and cannot support any form of virtualization,
289         // so there's no point running any of these tests.
290         assume().withMessage(
291                         "Virtualization not supported on Arm64 Cuttlefish/Goldfish."
292                                 + " b/341889915")
293                 .that(isCuttlefishArm64() || isGoldfishArm64())
294                 .isFalse();
295     }
296 
297     protected void assumeNoUpdatableVmSupport() throws VirtualMachineException {
298         assume().withMessage("Secretkeeper not supported").that(isUpdatableVmSupported()).isFalse();
299     }
300 
301     protected boolean isUpdatableVmSupported() throws VirtualMachineException {
302         // Pre-36 OS doesn't have VirtualMachineManager#isUpdatableVmSupported.
303         if (Build.VERSION.SDK_INT >= 35) {
304             return getVirtualMachineManager().isUpdatableVmSupported();
305         }
306         return false;
307     }
308 
ensureVmAttestationSupported()309     protected void ensureVmAttestationSupported() throws Exception {
310         // The first vendor API level is checked because VM attestation requires the VM DICE chain
311         // to be ROM-rooted.
312         int firstVendorApiLevel = getFirstVendorApiLevel();
313         boolean isRemoteAttestationSupported = isRemoteAttestationSupported();
314         if (firstVendorApiLevel >= 202504) {
315             assertWithMessage(
316                             "First vendor API '"
317                                     + firstVendorApiLevel
318                                     + "' (>=202504) must support VM remote attestation")
319                     .that(isRemoteAttestationSupported)
320                     .isTrue();
321         } else {
322             assumeTrue("Skip on VM remote attestation not supported", isRemoteAttestationSupported);
323         }
324     }
325 
isRemoteAttestationSupported()326     protected boolean isRemoteAttestationSupported() throws VirtualMachineException {
327         // Pre-36 OS doesn't have VirtualMachineManager#isRemoteAttestionSupported
328         if (Build.VERSION.SDK_INT >= 35) {
329             return getVirtualMachineManager().isRemoteAttestationSupported();
330         }
331         return false;
332     }
333 
334     public abstract static class VmEventListener implements VirtualMachineCallback {
335         private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
336         private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty();
337         private OptionalLong mKernelStartedNanoTime = OptionalLong.empty();
338         private OptionalLong mInitStartedNanoTime = OptionalLong.empty();
339         private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
340         private StringBuilder mConsoleOutput = new StringBuilder();
341         private StringBuilder mLogOutput = new StringBuilder();
342         private boolean mProcessedBootTimeMetrics = false;
343 
processBootTimeMetrics(String log)344         private synchronized void processBootTimeMetrics(String log) {
345             if (!mVcpuStartedNanoTime.isPresent()) {
346                 mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
347             }
348             if (log.contains("Starting payload...") && !mKernelStartedNanoTime.isPresent()) {
349                 mKernelStartedNanoTime = OptionalLong.of(System.nanoTime());
350             }
351             if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) {
352                 mInitStartedNanoTime = OptionalLong.of(System.nanoTime());
353             }
354             if (log.contains("microdroid_manager")
355                     && log.contains("executing main task")
356                     && !mPayloadStartedNanoTime.isPresent()) {
357                 mPayloadStartedNanoTime = OptionalLong.of(System.nanoTime());
358             }
359         }
360 
logVmOutputAndMonitorBootTimeMetrics( String tag, InputStream vmOutputStream, String name, StringBuilder result)361         private void logVmOutputAndMonitorBootTimeMetrics(
362                 String tag, InputStream vmOutputStream, String name, StringBuilder result) {
363             mProcessedBootTimeMetrics = true;
364             new Thread(
365                             () -> {
366                                 try {
367                                     BufferedReader reader =
368                                             new BufferedReader(
369                                                     new InputStreamReader(vmOutputStream));
370                                     String line;
371                                     while ((line = reader.readLine()) != null
372                                             && !Thread.interrupted()) {
373                                         processBootTimeMetrics(line);
374                                         Log.i(tag, name + ": " + line);
375                                         result.append(line + "\n");
376                                     }
377                                 } catch (Exception e) {
378                                     Log.w(tag, name, e);
379                                 }
380                             })
381                     .start();
382         }
383 
runToFinish(String logTag, VirtualMachine vm)384         public void runToFinish(String logTag, VirtualMachine vm)
385                 throws VirtualMachineException, InterruptedException {
386             vm.setCallback(mExecutorService, this);
387             vm.run();
388             if (vm.getConfig().isVmOutputCaptured()) {
389                 logVmOutputAndMonitorBootTimeMetrics(
390                         logTag, vm.getConsoleOutput(), "Console", mConsoleOutput);
391                 logVmOutputAndMonitorBootTimeMetrics(logTag, vm.getLogOutput(), "Log", mLogOutput);
392             }
393             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
394         }
395 
getVcpuStartedNanoTime()396         public OptionalLong getVcpuStartedNanoTime() {
397             return mVcpuStartedNanoTime;
398         }
399 
getKernelStartedNanoTime()400         public OptionalLong getKernelStartedNanoTime() {
401             return mKernelStartedNanoTime;
402         }
403 
getInitStartedNanoTime()404         public OptionalLong getInitStartedNanoTime() {
405             return mInitStartedNanoTime;
406         }
407 
getPayloadStartedNanoTime()408         public OptionalLong getPayloadStartedNanoTime() {
409             return mPayloadStartedNanoTime;
410         }
411 
getConsoleOutput()412         public String getConsoleOutput() {
413             return mConsoleOutput.toString();
414         }
415 
getLogOutput()416         public String getLogOutput() {
417             return mLogOutput.toString();
418         }
419 
hasProcessedBootTimeMetrics()420         public boolean hasProcessedBootTimeMetrics() {
421             return mProcessedBootTimeMetrics;
422         }
423 
424         // Stopping a virtual machine is like pulling the plug on a real computer. VM may be left in
425         // an inconsistent state.
426         // For a graceful shutdown, request the payload to call {@code exit()} and wait for
427         // VirtualMachineCallback#onPayloadFinished} to be called.
forceStop(VirtualMachine vm)428         protected void forceStop(VirtualMachine vm) {
429             try {
430                 vm.stop();
431             } catch (VirtualMachineException e) {
432                 throw new RuntimeException(e);
433             }
434         }
435 
436         @Override
onPayloadStarted(VirtualMachine vm)437         public void onPayloadStarted(VirtualMachine vm) {}
438 
439         @Override
onPayloadReady(VirtualMachine vm)440         public void onPayloadReady(VirtualMachine vm) {}
441 
442         @Override
onPayloadFinished(VirtualMachine vm, int exitCode)443         public void onPayloadFinished(VirtualMachine vm, int exitCode) {}
444 
445         @Override
onError(VirtualMachine vm, int errorCode, String message)446         public void onError(VirtualMachine vm, int errorCode, String message) {}
447 
448         @Override
449         @CallSuper
onStopped(VirtualMachine vm, int reason)450         public void onStopped(VirtualMachine vm, int reason) {
451             vm.clearCallback();
452             mExecutorService.shutdown();
453         }
454     }
455 
456     public enum BootTimeMetric {
457         TOTAL,
458         VM_START,
459         BOOTLOADER,
460         KERNEL,
461         USERSPACE,
462     }
463 
464     public static class BootResult {
465         public final boolean payloadStarted;
466         public final int deathReason;
467         public final long apiCallNanoTime;
468         public final long endToEndNanoTime;
469 
470         public final boolean processedBootTimeMetrics;
471         public final OptionalLong vcpuStartedNanoTime;
472         public final OptionalLong kernelStartedNanoTime;
473         public final OptionalLong initStartedNanoTime;
474         public final OptionalLong payloadStartedNanoTime;
475 
476         public final String consoleOutput;
477         public final String logOutput;
478 
BootResult( boolean payloadStarted, int deathReason, long apiCallNanoTime, long endToEndNanoTime, boolean processedBootTimeMetrics, OptionalLong vcpuStartedNanoTime, OptionalLong kernelStartedNanoTime, OptionalLong initStartedNanoTime, OptionalLong payloadStartedNanoTime, String consoleOutput, String logOutput)479         BootResult(
480                 boolean payloadStarted,
481                 int deathReason,
482                 long apiCallNanoTime,
483                 long endToEndNanoTime,
484                 boolean processedBootTimeMetrics,
485                 OptionalLong vcpuStartedNanoTime,
486                 OptionalLong kernelStartedNanoTime,
487                 OptionalLong initStartedNanoTime,
488                 OptionalLong payloadStartedNanoTime,
489                 String consoleOutput,
490                 String logOutput) {
491             this.apiCallNanoTime = apiCallNanoTime;
492             this.payloadStarted = payloadStarted;
493             this.deathReason = deathReason;
494             this.endToEndNanoTime = endToEndNanoTime;
495             this.processedBootTimeMetrics = processedBootTimeMetrics;
496             this.vcpuStartedNanoTime = vcpuStartedNanoTime;
497             this.kernelStartedNanoTime = kernelStartedNanoTime;
498             this.initStartedNanoTime = initStartedNanoTime;
499             this.payloadStartedNanoTime = payloadStartedNanoTime;
500             this.consoleOutput = consoleOutput;
501             this.logOutput = logOutput;
502         }
503 
getVcpuStartedNanoTime()504         private long getVcpuStartedNanoTime() {
505             return vcpuStartedNanoTime.getAsLong();
506         }
507 
getKernelStartedNanoTime()508         private long getKernelStartedNanoTime() {
509             // pvmfw emits log at the end which is used to estimate the kernelStart time.
510             // In case of no pvmfw run(non-protected mode), use vCPU started time instead.
511             return kernelStartedNanoTime.orElse(vcpuStartedNanoTime.getAsLong());
512         }
513 
getInitStartedNanoTime()514         private long getInitStartedNanoTime() {
515             return initStartedNanoTime.getAsLong();
516         }
517 
getPayloadStartedNanoTime()518         private long getPayloadStartedNanoTime() {
519             return payloadStartedNanoTime.getAsLong();
520         }
521 
getVMStartingElapsedNanoTime()522         public long getVMStartingElapsedNanoTime() {
523             return getVcpuStartedNanoTime() - apiCallNanoTime;
524         }
525 
getBootloaderElapsedNanoTime()526         public long getBootloaderElapsedNanoTime() {
527             return getKernelStartedNanoTime() - getVcpuStartedNanoTime();
528         }
529 
getKernelElapsedNanoTime()530         public long getKernelElapsedNanoTime() {
531             return getInitStartedNanoTime() - getKernelStartedNanoTime();
532         }
533 
getUserspaceElapsedNanoTime()534         public long getUserspaceElapsedNanoTime() {
535             return getPayloadStartedNanoTime() - getInitStartedNanoTime();
536         }
537 
getBootTimeMetricNanoTime(BootTimeMetric metric)538         public OptionalLong getBootTimeMetricNanoTime(BootTimeMetric metric) {
539             if (metric == BootTimeMetric.TOTAL) {
540                 return OptionalLong.of(endToEndNanoTime);
541             }
542 
543             if (processedBootTimeMetrics) {
544                 switch (metric) {
545                     case VM_START:
546                         return OptionalLong.of(getVMStartingElapsedNanoTime());
547                     case BOOTLOADER:
548                         return OptionalLong.of(getBootloaderElapsedNanoTime());
549                     case KERNEL:
550                         return OptionalLong.of(getKernelElapsedNanoTime());
551                     case USERSPACE:
552                         return OptionalLong.of(getUserspaceElapsedNanoTime());
553                 }
554             }
555 
556             return OptionalLong.empty();
557         }
558     }
559 
tryBootVm(String logTag, String vmName)560     public BootResult tryBootVm(String logTag, String vmName)
561             throws VirtualMachineException, InterruptedException {
562         VirtualMachine vm = getVirtualMachineManager().get(vmName);
563         return tryBootVm(logTag, vm);
564     }
565 
tryBootVm(String logTag, VirtualMachine vm)566     public BootResult tryBootVm(String logTag, VirtualMachine vm)
567             throws VirtualMachineException, InterruptedException {
568         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
569         final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
570         final CompletableFuture<Long> endTime = new CompletableFuture<>();
571         VmEventListener listener =
572                 new VmEventListener() {
573                     @Override
574                     public void onPayloadStarted(VirtualMachine vm) {
575                         endTime.complete(System.nanoTime());
576                         payloadStarted.complete(true);
577                         forceStop(vm);
578                     }
579 
580                     @Override
581                     public void onStopped(VirtualMachine vm, int reason) {
582                         deathReason.complete(reason);
583                         super.onStopped(vm, reason);
584                     }
585                 };
586         long apiCallNanoTime = System.nanoTime();
587         listener.runToFinish(logTag, vm);
588         return new BootResult(
589                 payloadStarted.getNow(false),
590                 deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
591                 apiCallNanoTime,
592                 endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
593                 listener.hasProcessedBootTimeMetrics(),
594                 listener.getVcpuStartedNanoTime(),
595                 listener.getKernelStartedNanoTime(),
596                 listener.getInitStartedNanoTime(),
597                 listener.getPayloadStartedNanoTime(),
598                 listener.getConsoleOutput(),
599                 listener.getLogOutput());
600     }
601 
602     /** Execute a command. Returns stdout. */
runInShell(String tag, UiAutomation uiAutomation, String command)603     protected String runInShell(String tag, UiAutomation uiAutomation, String command) {
604         try (InputStream is =
605                         new ParcelFileDescriptor.AutoCloseInputStream(
606                                 uiAutomation.executeShellCommand(command));
607                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
608             is.transferTo(out);
609             String stdout = out.toString("UTF-8");
610             Log.i(tag, "Got stdout : " + stdout);
611             return stdout;
612         } catch (IOException e) {
613             Log.e(tag, "Error executing: " + command, e);
614             throw new RuntimeException("Failed to run the command.", e);
615         }
616     }
617 
618     /** Execute a command. Returns the concatenation of stdout and stderr. */
runInShellWithStderr(String tag, UiAutomation uiAutomation, String command)619     protected String runInShellWithStderr(String tag, UiAutomation uiAutomation, String command) {
620         ParcelFileDescriptor[] files = uiAutomation.executeShellCommandRwe(command);
621         try (InputStream stdout = new ParcelFileDescriptor.AutoCloseInputStream(files[0]);
622                 InputStream stderr = new ParcelFileDescriptor.AutoCloseInputStream(files[2]);
623                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
624             files[1].close(); // The command's stdin
625             stdout.transferTo(out);
626             stderr.transferTo(out);
627             String output = out.toString("UTF-8");
628             Log.i(tag, "Got stdout + stderr : " + output);
629             return output;
630         } catch (IOException e) {
631             Log.e(tag, "Error executing: " + command, e);
632             throw new RuntimeException("Failed to run the command.", e);
633         }
634     }
635 
636     protected static class TestResults {
637         public Exception mException;
638         public Integer mAddInteger;
639         public String mAppRunProp;
640         public String mSublibRunProp;
641         public String mExtraApkTestProp;
642         public String mApkContentsPath;
643         public String mEncryptedStoragePath;
644         public long mEncryptedStorageSize;
645         public String[] mEffectiveCapabilities;
646         public int mUid;
647         public String mFileContent;
648         public byte[] mBcc;
649         public long[] mTimings;
650         public int mFileMode;
651         public int mMountFlags;
652         public String mConsoleInput;
653         public byte[] mInstanceSecret;
654         public int mPageSize;
655         public byte[] mPayloadRpData;
656         public boolean mIsNewInstance;
657 
assertNoException()658         public void assertNoException() {
659             if (mException != null) {
660                 // Rethrow, wrapped in a new exception, so we get stack traces of the original
661                 // failure as well as the body of the test.
662                 throw new RuntimeException(mException);
663             }
664         }
665     }
666 
runVmAttestationService( String logTag, VirtualMachine vm, byte[] challenge, byte[] messageToSign)667     protected SigningResult runVmAttestationService(
668             String logTag, VirtualMachine vm, byte[] challenge, byte[] messageToSign)
669             throws Exception {
670 
671         CompletableFuture<Exception> exception = new CompletableFuture<>();
672         CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
673         CompletableFuture<SigningResult> signingResultFuture = new CompletableFuture<>();
674         VmEventListener listener =
675                 new VmEventListener() {
676                     @Override
677                     public void onPayloadReady(VirtualMachine vm) {
678                         payloadReady.complete(true);
679                         try {
680                             IAttestationService service =
681                                     IAttestationService.Stub.asInterface(
682                                             vm.connectToVsockServer(IAttestationService.PORT));
683                             signingResultFuture.complete(
684                                     service.signWithAttestationKey(challenge, messageToSign));
685                         } catch (Exception e) {
686                             exception.complete(e);
687                         } finally {
688                             forceStop(vm);
689                         }
690                     }
691                 };
692         listener.runToFinish(TAG, vm);
693 
694         assertThat(payloadReady.getNow(false)).isTrue();
695         assertThat(exception.getNow(null)).isNull();
696         SigningResult signingResult = signingResultFuture.getNow(null);
697         assertThat(signingResult).isNotNull();
698         return signingResult;
699     }
700 
runVmTestService( String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun)701     protected TestResults runVmTestService(
702             String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun)
703             throws Exception {
704         CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
705         CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
706         CompletableFuture<Boolean> payloadFinished = new CompletableFuture<>();
707         TestResults testResults = new TestResults();
708         VmEventListener listener =
709                 new VmEventListener() {
710                     ITestService mTestService = null;
711 
712                     private void initializeTestService(VirtualMachine vm) {
713                         try {
714                             mTestService =
715                                     ITestService.Stub.asInterface(
716                                             vm.connectToVsockServer(ITestService.PORT));
717                             // Make sure linkToDeath works, and include it in the log in case it's
718                             // helpful.
719                             mTestService
720                                     .asBinder()
721                                     .linkToDeath(
722                                             () -> Log.i(logTag, "ITestService binder died"), 0);
723                         } catch (Exception e) {
724                             testResults.mException = e;
725                         }
726                     }
727 
728                     private void testVMService(VirtualMachine vm) {
729                         try {
730                             if (mTestService == null) initializeTestService(vm);
731                             testsToRun.runTests(mTestService, testResults);
732                         } catch (Exception e) {
733                             testResults.mException = e;
734                         }
735                     }
736 
737                     private void quitVMService() {
738                         try {
739                             mTestService.quit();
740                         } catch (Exception e) {
741                             testResults.mException = e;
742                         }
743                     }
744 
745                     @Override
746                     public void onPayloadReady(VirtualMachine vm) {
747                         Log.i(logTag, "onPayloadReady");
748                         payloadReady.complete(true);
749                         testVMService(vm);
750                         quitVMService();
751                     }
752 
753                     @Override
754                     public void onPayloadStarted(VirtualMachine vm) {
755                         Log.i(logTag, "onPayloadStarted");
756                         payloadStarted.complete(true);
757                     }
758 
759                     @Override
760                     public void onPayloadFinished(VirtualMachine vm, int exitCode) {
761                         Log.i(logTag, "onPayloadFinished: " + exitCode);
762                         payloadFinished.complete(true);
763                     }
764                 };
765 
766         listener.runToFinish(logTag, vm);
767         assertThat(payloadStarted.getNow(false)).isTrue();
768         assertThat(payloadReady.getNow(false)).isTrue();
769         assertThat(payloadFinished.getNow(false)).isTrue();
770         return testResults;
771     }
772 
getAvailableMemory()773     protected long getAvailableMemory() {
774         ActivityManager am = getContext().getSystemService(ActivityManager.class);
775         ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
776         am.getMemoryInfo(memoryInfo);
777         return memoryInfo.availMem;
778     }
779 
minMemoryRequired()780     protected long minMemoryRequired() {
781         assertThat(Build.SUPPORTED_ABIS).isNotEmpty();
782         String primaryAbi = Build.SUPPORTED_ABIS[0];
783         switch (primaryAbi) {
784             case "x86_64":
785                 return MIN_MEM_X86_64;
786             case "arm64-v8a":
787             case "arm64-v8a-hwasan":
788                 return MIN_MEM_ARM64;
789         }
790         throw new AssertionError("Unsupported ABI: " + primaryAbi);
791     }
792 
793     @FunctionalInterface
794     protected interface RunTestsAgainstTestService {
runTests(ITestService testService, TestResults testResults)795         void runTests(ITestService testService, TestResults testResults) throws Exception;
796     }
797 
assumeFeatureEnabled(String featureName)798     protected void assumeFeatureEnabled(String featureName) throws Exception {
799         assumeTrue(featureName + " not enabled", isFeatureEnabled(featureName));
800     }
801 
isFeatureEnabled(String featureName)802     protected boolean isFeatureEnabled(String featureName) throws Exception {
803         return getVirtualMachineManager().isFeatureEnabled(featureName);
804     }
805 
assumeProtectedVM()806     protected void assumeProtectedVM() {
807         assumeTrue("Skip on non-protected VM", mProtectedVm);
808     }
809 
assumeNonProtectedVM()810     protected void assumeNonProtectedVM() {
811         assumeFalse("Skip on protected VM", mProtectedVm);
812     }
813 }
814