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