• 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_VIRTUALIZATION_FRAMEWORK;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.TruthJUnit.assume;
22 
23 import android.app.Instrumentation;
24 import android.app.UiAutomation;
25 import android.content.Context;
26 import android.os.ParcelFileDescriptor;
27 import android.os.SystemProperties;
28 import android.system.Os;
29 import android.system.virtualmachine.VirtualMachine;
30 import android.system.virtualmachine.VirtualMachineCallback;
31 import android.system.virtualmachine.VirtualMachineConfig;
32 import android.system.virtualmachine.VirtualMachineException;
33 import android.system.virtualmachine.VirtualMachineManager;
34 import android.util.Log;
35 
36 import androidx.annotation.CallSuper;
37 import androidx.test.core.app.ApplicationProvider;
38 import androidx.test.platform.app.InstrumentationRegistry;
39 
40 import com.android.microdroid.test.common.DeviceProperties;
41 import com.android.microdroid.test.common.MetricsProcessor;
42 import com.android.microdroid.testservice.ITestService;
43 
44 import java.io.BufferedReader;
45 import java.io.ByteArrayOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.InputStreamReader;
49 import java.util.OptionalLong;
50 import java.util.concurrent.CompletableFuture;
51 import java.util.concurrent.ExecutorService;
52 import java.util.concurrent.Executors;
53 import java.util.concurrent.TimeUnit;
54 
55 public abstract class MicrodroidDeviceTestBase {
56     private static final String TAG = "MicrodroidDeviceTestBase";
57     private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP";
58 
isCuttlefish()59     public static boolean isCuttlefish() {
60         return getDeviceProperties().isCuttlefish();
61     }
62 
isUserBuild()63     public static boolean isUserBuild() {
64         return getDeviceProperties().isUserBuild();
65     }
66 
getMetricPrefix()67     public static String getMetricPrefix() {
68         return MetricsProcessor.getMetricPrefix(getDeviceProperties().getMetricsTag());
69     }
70 
getDeviceProperties()71     private static DeviceProperties getDeviceProperties() {
72         return DeviceProperties.create(SystemProperties::get);
73     }
74 
grantPermission(String permission)75     protected final void grantPermission(String permission) {
76         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
77         UiAutomation uiAutomation = instrumentation.getUiAutomation();
78         uiAutomation.grantRuntimePermission(instrumentation.getContext().getPackageName(),
79                 permission);
80     }
81 
revokePermission(String permission)82     protected final void revokePermission(String permission) {
83         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
84         UiAutomation uiAutomation = instrumentation.getUiAutomation();
85         uiAutomation.revokeRuntimePermission(instrumentation.getContext().getPackageName(),
86                 permission);
87     }
88 
setMaxPerformanceTaskProfile()89     protected final void setMaxPerformanceTaskProfile() throws IOException {
90         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
91         UiAutomation uiAutomation = instrumentation.getUiAutomation();
92         String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE;
93         String out = runInShell(TAG, uiAutomation, cmd).trim();
94         String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!";
95         if (!expect.equals(out)) {
96             throw new IOException("Could not apply max performance task profile: " + out);
97         }
98     }
99 
100     private Context mCtx;
101     private boolean mProtectedVm;
102 
getContext()103     protected Context getContext() {
104         return mCtx;
105     }
106 
getVirtualMachineManager()107     public VirtualMachineManager getVirtualMachineManager() {
108         return mCtx.getSystemService(VirtualMachineManager.class);
109     }
110 
newVmConfigBuilder()111     public VirtualMachineConfig.Builder newVmConfigBuilder() {
112         return new VirtualMachineConfig.Builder(mCtx).setProtectedVm(mProtectedVm);
113     }
114 
isProtectedVm()115     protected final boolean isProtectedVm() {
116         return mProtectedVm;
117     }
118 
119     /**
120      * Creates a new virtual machine, potentially removing an existing virtual machine with given
121      * name.
122      */
forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)123     public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
124             throws VirtualMachineException {
125         final VirtualMachineManager vmm = getVirtualMachineManager();
126         VirtualMachine existingVm = vmm.get(name);
127         if (existingVm != null) {
128             vmm.delete(name);
129         }
130         return vmm.create(name, config);
131     }
132 
prepareTestSetup(boolean protectedVm)133     public void prepareTestSetup(boolean protectedVm) {
134         mCtx = ApplicationProvider.getApplicationContext();
135         assume().withMessage("Device doesn't support AVF")
136                 .that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK))
137                 .isTrue();
138 
139         mProtectedVm = protectedVm;
140 
141         int capabilities = getVirtualMachineManager().getCapabilities();
142         if (protectedVm) {
143             assume().withMessage("Skip where protected VMs aren't supported")
144                     .that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
145                     .isNotEqualTo(0);
146         } else {
147             assume().withMessage("Skip where VMs aren't supported")
148                     .that(capabilities & VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM)
149                     .isNotEqualTo(0);
150         }
151     }
152 
153     public abstract static class VmEventListener implements VirtualMachineCallback {
154         private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
155         private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty();
156         private OptionalLong mKernelStartedNanoTime = OptionalLong.empty();
157         private OptionalLong mInitStartedNanoTime = OptionalLong.empty();
158         private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
159         private StringBuilder mConsoleOutput = new StringBuilder();
160         private StringBuilder mLogOutput = new StringBuilder();
161         private boolean mProcessedBootTimeMetrics = false;
162 
processBootTimeMetrics(String log)163         private void processBootTimeMetrics(String log) {
164             if (!mVcpuStartedNanoTime.isPresent()) {
165                 mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
166             }
167             if (log.contains("Starting payload...") && !mKernelStartedNanoTime.isPresent()) {
168                 mKernelStartedNanoTime = OptionalLong.of(System.nanoTime());
169             }
170             if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) {
171                 mInitStartedNanoTime = OptionalLong.of(System.nanoTime());
172             }
173             if (log.contains("microdroid_manager") && log.contains("executing main task")
174                     && !mPayloadStartedNanoTime.isPresent()) {
175                 mPayloadStartedNanoTime = OptionalLong.of(System.nanoTime());
176             }
177         }
178 
logVmOutputAndMonitorBootTimeMetrics( String tag, InputStream vmOutputStream, String name, StringBuilder result, boolean monitorEvents)179         private void logVmOutputAndMonitorBootTimeMetrics(
180                 String tag,
181                 InputStream vmOutputStream,
182                 String name,
183                 StringBuilder result,
184                 boolean monitorEvents) {
185             mProcessedBootTimeMetrics |= monitorEvents;
186             new Thread(
187                             () -> {
188                                 try {
189                                     BufferedReader reader =
190                                             new BufferedReader(
191                                                     new InputStreamReader(vmOutputStream));
192                                     String line;
193                                     while ((line = reader.readLine()) != null
194                                             && !Thread.interrupted()) {
195                                         if (monitorEvents) processBootTimeMetrics(line);
196                                         Log.i(tag, name + ": " + line);
197                                         result.append(line + "\n");
198                                     }
199                                 } catch (Exception e) {
200                                     Log.w(tag, name, e);
201                                 }
202                             })
203                     .start();
204         }
205 
logVmOutputAndMonitorBootTimeMetrics( String tag, InputStream vmOutputStream, String name, StringBuilder result)206         private void logVmOutputAndMonitorBootTimeMetrics(
207                 String tag, InputStream vmOutputStream, String name, StringBuilder result) {
208             logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, true);
209         }
210 
211         /** Copy output from the VM to logcat. This is helpful when things go wrong. */
logVmOutput( String tag, InputStream vmOutputStream, String name, StringBuilder result)212         protected void logVmOutput(
213                 String tag, InputStream vmOutputStream, String name, StringBuilder result) {
214             logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, false);
215         }
216 
runToFinish(String logTag, VirtualMachine vm)217         public void runToFinish(String logTag, VirtualMachine vm)
218                 throws VirtualMachineException, InterruptedException {
219             vm.setCallback(mExecutorService, this);
220             vm.run();
221             if (vm.getConfig().isVmOutputCaptured()) {
222                 logVmOutputAndMonitorBootTimeMetrics(
223                         logTag, vm.getConsoleOutput(), "Console", mConsoleOutput);
224                 logVmOutput(logTag, vm.getLogOutput(), "Log", mLogOutput);
225             }
226             mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
227         }
228 
getVcpuStartedNanoTime()229         public OptionalLong getVcpuStartedNanoTime() {
230             return mVcpuStartedNanoTime;
231         }
232 
getKernelStartedNanoTime()233         public OptionalLong getKernelStartedNanoTime() {
234             return mKernelStartedNanoTime;
235         }
236 
getInitStartedNanoTime()237         public OptionalLong getInitStartedNanoTime() {
238             return mInitStartedNanoTime;
239         }
240 
getPayloadStartedNanoTime()241         public OptionalLong getPayloadStartedNanoTime() {
242             return mPayloadStartedNanoTime;
243         }
244 
getConsoleOutput()245         public String getConsoleOutput() {
246             return mConsoleOutput.toString();
247         }
248 
getLogOutput()249         public String getLogOutput() {
250             return mLogOutput.toString();
251         }
252 
hasProcessedBootTimeMetrics()253         public boolean hasProcessedBootTimeMetrics() {
254             return mProcessedBootTimeMetrics;
255         }
256 
forceStop(VirtualMachine vm)257         protected void forceStop(VirtualMachine vm) {
258             try {
259                 vm.stop();
260             } catch (VirtualMachineException e) {
261                 throw new RuntimeException(e);
262             }
263         }
264 
265         @Override
onPayloadStarted(VirtualMachine vm)266         public void onPayloadStarted(VirtualMachine vm) {}
267 
268         @Override
onPayloadReady(VirtualMachine vm)269         public void onPayloadReady(VirtualMachine vm) {}
270 
271         @Override
onPayloadFinished(VirtualMachine vm, int exitCode)272         public void onPayloadFinished(VirtualMachine vm, int exitCode) {}
273 
274         @Override
onError(VirtualMachine vm, int errorCode, String message)275         public void onError(VirtualMachine vm, int errorCode, String message) {}
276 
277         @Override
278         @CallSuper
onStopped(VirtualMachine vm, int reason)279         public void onStopped(VirtualMachine vm, int reason) {
280             vm.clearCallback();
281             mExecutorService.shutdown();
282         }
283     }
284 
285     public enum BootTimeMetric {
286         TOTAL,
287         VM_START,
288         BOOTLOADER,
289         KERNEL,
290         USERSPACE,
291     }
292 
293     public static class BootResult {
294         public final boolean payloadStarted;
295         public final int deathReason;
296         public final long apiCallNanoTime;
297         public final long endToEndNanoTime;
298 
299         public final boolean processedBootTimeMetrics;
300         public final OptionalLong vcpuStartedNanoTime;
301         public final OptionalLong kernelStartedNanoTime;
302         public final OptionalLong initStartedNanoTime;
303         public final OptionalLong payloadStartedNanoTime;
304 
305         public final String consoleOutput;
306         public final String logOutput;
307 
BootResult( boolean payloadStarted, int deathReason, long apiCallNanoTime, long endToEndNanoTime, boolean processedBootTimeMetrics, OptionalLong vcpuStartedNanoTime, OptionalLong kernelStartedNanoTime, OptionalLong initStartedNanoTime, OptionalLong payloadStartedNanoTime, String consoleOutput, String logOutput)308         BootResult(
309                 boolean payloadStarted,
310                 int deathReason,
311                 long apiCallNanoTime,
312                 long endToEndNanoTime,
313                 boolean processedBootTimeMetrics,
314                 OptionalLong vcpuStartedNanoTime,
315                 OptionalLong kernelStartedNanoTime,
316                 OptionalLong initStartedNanoTime,
317                 OptionalLong payloadStartedNanoTime,
318                 String consoleOutput,
319                 String logOutput) {
320             this.apiCallNanoTime = apiCallNanoTime;
321             this.payloadStarted = payloadStarted;
322             this.deathReason = deathReason;
323             this.endToEndNanoTime = endToEndNanoTime;
324             this.processedBootTimeMetrics = processedBootTimeMetrics;
325             this.vcpuStartedNanoTime = vcpuStartedNanoTime;
326             this.kernelStartedNanoTime = kernelStartedNanoTime;
327             this.initStartedNanoTime = initStartedNanoTime;
328             this.payloadStartedNanoTime = payloadStartedNanoTime;
329             this.consoleOutput = consoleOutput;
330             this.logOutput = logOutput;
331         }
332 
getVcpuStartedNanoTime()333         private long getVcpuStartedNanoTime() {
334             return vcpuStartedNanoTime.getAsLong();
335         }
336 
getKernelStartedNanoTime()337         private long getKernelStartedNanoTime() {
338             // pvmfw emits log at the end which is used to estimate the kernelStart time.
339             // In case of no pvmfw run(non-protected mode), use vCPU started time instead.
340             return kernelStartedNanoTime.orElse(vcpuStartedNanoTime.getAsLong());
341         }
342 
getInitStartedNanoTime()343         private long getInitStartedNanoTime() {
344             return initStartedNanoTime.getAsLong();
345         }
346 
getPayloadStartedNanoTime()347         private long getPayloadStartedNanoTime() {
348             return payloadStartedNanoTime.getAsLong();
349         }
350 
getVMStartingElapsedNanoTime()351         public long getVMStartingElapsedNanoTime() {
352             return getVcpuStartedNanoTime() - apiCallNanoTime;
353         }
354 
getBootloaderElapsedNanoTime()355         public long getBootloaderElapsedNanoTime() {
356             return getKernelStartedNanoTime() - getVcpuStartedNanoTime();
357         }
358 
getKernelElapsedNanoTime()359         public long getKernelElapsedNanoTime() {
360             return getInitStartedNanoTime() - getKernelStartedNanoTime();
361         }
362 
getUserspaceElapsedNanoTime()363         public long getUserspaceElapsedNanoTime() {
364             return getPayloadStartedNanoTime() - getInitStartedNanoTime();
365         }
366 
getBootTimeMetricNanoTime(BootTimeMetric metric)367         public OptionalLong getBootTimeMetricNanoTime(BootTimeMetric metric) {
368             if (metric == BootTimeMetric.TOTAL) {
369                 return OptionalLong.of(endToEndNanoTime);
370             }
371 
372             if (processedBootTimeMetrics) {
373                 switch (metric) {
374                     case VM_START:
375                         return OptionalLong.of(getVMStartingElapsedNanoTime());
376                     case BOOTLOADER:
377                         return OptionalLong.of(getBootloaderElapsedNanoTime());
378                     case KERNEL:
379                         return OptionalLong.of(getKernelElapsedNanoTime());
380                     case USERSPACE:
381                         return OptionalLong.of(getUserspaceElapsedNanoTime());
382                 }
383             }
384 
385             return OptionalLong.empty();
386         }
387     }
388 
tryBootVm(String logTag, String vmName)389     public BootResult tryBootVm(String logTag, String vmName)
390             throws VirtualMachineException, InterruptedException {
391         VirtualMachine vm = getVirtualMachineManager().get(vmName);
392         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
393         final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
394         final CompletableFuture<Long> endTime = new CompletableFuture<>();
395         VmEventListener listener =
396                 new VmEventListener() {
397                     @Override
398                     public void onPayloadStarted(VirtualMachine vm) {
399                         endTime.complete(System.nanoTime());
400                         payloadStarted.complete(true);
401                         forceStop(vm);
402                     }
403 
404                     @Override
405                     public void onStopped(VirtualMachine vm, int reason) {
406                         deathReason.complete(reason);
407                         super.onStopped(vm, reason);
408                     }
409                 };
410         long apiCallNanoTime = System.nanoTime();
411         listener.runToFinish(logTag, vm);
412         return new BootResult(
413                 payloadStarted.getNow(false),
414                 deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
415                 apiCallNanoTime,
416                 endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
417                 listener.hasProcessedBootTimeMetrics(),
418                 listener.getVcpuStartedNanoTime(),
419                 listener.getKernelStartedNanoTime(),
420                 listener.getInitStartedNanoTime(),
421                 listener.getPayloadStartedNanoTime(),
422                 listener.getConsoleOutput(),
423                 listener.getLogOutput());
424     }
425 
426     /** Execute a command. Returns stdout. */
runInShell(String tag, UiAutomation uiAutomation, String command)427     protected String runInShell(String tag, UiAutomation uiAutomation, String command) {
428         try (InputStream is =
429                         new ParcelFileDescriptor.AutoCloseInputStream(
430                                 uiAutomation.executeShellCommand(command));
431                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
432             is.transferTo(out);
433             String stdout = out.toString("UTF-8");
434             Log.i(tag, "Got stdout : " + stdout);
435             return stdout;
436         } catch (IOException e) {
437             Log.e(tag, "Error executing: " + command, e);
438             throw new RuntimeException("Failed to run the command.", e);
439         }
440     }
441 
442     /** Execute a command. Returns the concatenation of stdout and stderr. */
runInShellWithStderr(String tag, UiAutomation uiAutomation, String command)443     protected String runInShellWithStderr(String tag, UiAutomation uiAutomation, String command) {
444         ParcelFileDescriptor[] files = uiAutomation.executeShellCommandRwe(command);
445         try (InputStream stdout = new ParcelFileDescriptor.AutoCloseInputStream(files[0]);
446                 InputStream stderr = new ParcelFileDescriptor.AutoCloseInputStream(files[2]);
447                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
448             files[1].close(); // The command's stdin
449             stdout.transferTo(out);
450             stderr.transferTo(out);
451             String output = out.toString("UTF-8");
452             Log.i(tag, "Got output : " + stdout);
453             return output;
454         } catch (IOException e) {
455             Log.e(tag, "Error executing: " + command, e);
456             throw new RuntimeException("Failed to run the command.", e);
457         }
458     }
459 
460     protected static class TestResults {
461         public Exception mException;
462         public Integer mAddInteger;
463         public String mAppRunProp;
464         public String mSublibRunProp;
465         public String mExtraApkTestProp;
466         public String mApkContentsPath;
467         public String mEncryptedStoragePath;
468         public String[] mEffectiveCapabilities;
469         public String mFileContent;
470         public byte[] mBcc;
471         public long[] mTimings;
472         public int mFileMode;
473         public int mMountFlags;
474 
assertNoException()475         public void assertNoException() {
476             if (mException != null) {
477                 // Rethrow, wrapped in a new exception, so we get stack traces of the original
478                 // failure as well as the body of the test.
479                 throw new RuntimeException(mException);
480             }
481         }
482     }
483 
runVmTestService( String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun)484     protected TestResults runVmTestService(
485             String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun)
486             throws Exception {
487         CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
488         CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
489         CompletableFuture<Boolean> payloadFinished = new CompletableFuture<>();
490         TestResults testResults = new TestResults();
491         VmEventListener listener =
492                 new VmEventListener() {
493                     ITestService mTestService = null;
494 
495                     private void initializeTestService(VirtualMachine vm) {
496                         try {
497                             mTestService =
498                                     ITestService.Stub.asInterface(
499                                             vm.connectToVsockServer(ITestService.SERVICE_PORT));
500                             // Make sure linkToDeath works, and include it in the log in case it's
501                             // helpful.
502                             mTestService
503                                     .asBinder()
504                                     .linkToDeath(
505                                             () -> Log.i(logTag, "ITestService binder died"), 0);
506                         } catch (Exception e) {
507                             testResults.mException = e;
508                         }
509                     }
510 
511                     private void testVMService(VirtualMachine vm) {
512                         try {
513                             if (mTestService == null) initializeTestService(vm);
514                             testsToRun.runTests(mTestService, testResults);
515                         } catch (Exception e) {
516                             testResults.mException = e;
517                         }
518                     }
519 
520                     private void quitVMService() {
521                         try {
522                             mTestService.quit();
523                         } catch (Exception e) {
524                             testResults.mException = e;
525                         }
526                     }
527 
528                     @Override
529                     public void onPayloadReady(VirtualMachine vm) {
530                         Log.i(logTag, "onPayloadReady");
531                         payloadReady.complete(true);
532                         testVMService(vm);
533                         quitVMService();
534                     }
535 
536                     @Override
537                     public void onPayloadStarted(VirtualMachine vm) {
538                         Log.i(logTag, "onPayloadStarted");
539                         payloadStarted.complete(true);
540                     }
541 
542                     @Override
543                     public void onPayloadFinished(VirtualMachine vm, int exitCode) {
544                         Log.i(logTag, "onPayloadFinished: " + exitCode);
545                         payloadFinished.complete(true);
546                         forceStop(vm);
547                     }
548                 };
549 
550         listener.runToFinish(logTag, vm);
551         assertThat(payloadStarted.getNow(false)).isTrue();
552         assertThat(payloadReady.getNow(false)).isTrue();
553         assertThat(payloadFinished.getNow(false)).isTrue();
554         return testResults;
555     }
556 
557     @FunctionalInterface
558     protected interface RunTestsAgainstTestService {
runTests(ITestService testService, TestResults testResults)559         void runTests(ITestService testService, TestResults testResults) throws Exception;
560     }
561 }
562