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