Merge "Don't print output on the binder thread"
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index fcd6753..0ade0ba 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,8 +16,8 @@
package com.android.virt.fs;
-import static com.android.microdroid.test.CommandResultSubject.assertThat;
-import static com.android.microdroid.test.LogArchiver.archiveLogThenDelete;
+import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.host.LogArchiver.archiveLogThenDelete;
import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
@@ -32,7 +32,7 @@
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.PollingCheck;
-import com.android.microdroid.test.CommandRunner;
+import com.android.microdroid.test.host.CommandRunner;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index b4f55f9..21b2ecd 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -26,7 +26,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import com.android.microdroid.test.MicrodroidDeviceTestBase;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
import org.junit.Before;
import org.junit.Test;
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 4f33afd..d8504f7 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -16,8 +16,8 @@
package android.compos.test;
-import static com.android.microdroid.test.CommandResultSubject.assertThat;
-import static com.android.microdroid.test.CommandResultSubject.command_results;
+import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.host.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertThat;
@@ -25,8 +25,8 @@
import android.platform.test.annotations.RootPermissionTest;
-import com.android.microdroid.test.CommandRunner;
-import com.android.microdroid.test.MicrodroidHostTestCaseBase;
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 747e98c..6266c18 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -38,7 +38,6 @@
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.android.microdroid.testservice.ITestService;
@@ -64,44 +63,39 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- Button runStopButton = (Button) findViewById(R.id.runStopButton);
- TextView consoleView = (TextView) findViewById(R.id.consoleOutput);
- TextView logView = (TextView) findViewById(R.id.logOutput);
- TextView payloadView = (TextView) findViewById(R.id.payloadOutput);
- ScrollView scrollConsoleView = (ScrollView) findViewById(R.id.scrollConsoleOutput);
- ScrollView scrollLogView = (ScrollView) findViewById(R.id.scrollLogOutput);
+ Button runStopButton = findViewById(R.id.runStopButton);
+ TextView consoleView = findViewById(R.id.consoleOutput);
+ TextView logView = findViewById(R.id.logOutput);
+ TextView payloadView = findViewById(R.id.payloadOutput);
+ ScrollView scrollConsoleView = findViewById(R.id.scrollConsoleOutput);
+ ScrollView scrollLogView = findViewById(R.id.scrollLogOutput);
VirtualMachineModel model = new ViewModelProvider(this).get(VirtualMachineModel.class);
// When the button is clicked, run or stop the VM
runStopButton.setOnClickListener(
- new View.OnClickListener() {
- public void onClick(View v) {
- if (model.getStatus().getValue() == VirtualMachine.Status.RUNNING) {
- model.stop();
- } else {
- CheckBox debugModeCheckBox = (CheckBox) findViewById(R.id.debugMode);
- final boolean debug = debugModeCheckBox.isChecked();
- model.run(debug);
- }
+ v -> {
+ if (model.getStatus().getValue() == VirtualMachine.Status.RUNNING) {
+ model.stop();
+ } else {
+ CheckBox debugModeCheckBox = (CheckBox) findViewById(R.id.debugMode);
+ final boolean debug = debugModeCheckBox.isChecked();
+ model.run(debug);
}
});
// When the VM status is updated, change the label of the button
model.getStatus()
.observeForever(
- new Observer<VirtualMachine.Status>() {
- @Override
- public void onChanged(VirtualMachine.Status status) {
- if (status == VirtualMachine.Status.RUNNING) {
- runStopButton.setText("Stop");
- // Clear the outputs from the previous run
- consoleView.setText("");
- logView.setText("");
- payloadView.setText("");
- } else {
- runStopButton.setText("Run");
- }
+ status -> {
+ if (status == VirtualMachine.Status.RUNNING) {
+ runStopButton.setText("Stop");
+ // Clear the outputs from the previous run
+ consoleView.setText("");
+ logView.setText("");
+ payloadView.setText("");
+ } else {
+ runStopButton.setText("Run");
}
});
@@ -109,30 +103,19 @@
// corresponding text view.
model.getConsoleOutput()
.observeForever(
- new Observer<String>() {
- @Override
- public void onChanged(String line) {
- consoleView.append(line + "\n");
- scrollConsoleView.fullScroll(View.FOCUS_DOWN);
- }
+ line -> {
+ consoleView.append(line + "\n");
+ scrollConsoleView.fullScroll(View.FOCUS_DOWN);
});
model.getLogOutput()
.observeForever(
- new Observer<String>() {
- @Override
- public void onChanged(String line) {
- logView.append(line + "\n");
- scrollLogView.fullScroll(View.FOCUS_DOWN);
- }
+ line -> {
+ logView.append(line + "\n");
+ scrollLogView.fullScroll(View.FOCUS_DOWN);
});
model.getPayloadOutput()
.observeForever(
- new Observer<String>() {
- @Override
- public void onChanged(String line) {
- payloadView.append(line + "\n");
- }
- });
+ line -> payloadView.append(line + "\n"));
}
/** Reads data from an input stream and posts it to the output data */
@@ -178,7 +161,6 @@
/** Runs a VM */
public void run(boolean debug) {
// Create a VM and run it.
- // TODO(jiyong): remove the call to idsigPath
mExecutorService = Executors.newFixedThreadPool(4);
VirtualMachineCallback callback =
@@ -274,7 +256,7 @@
}
@Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {
+ public void onDied(VirtualMachine vm, int reason) {
mService.shutdownNow();
mStatus.postValue(VirtualMachine.Status.STOPPED);
}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 0d1cd91..bf78202 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -10,6 +10,7 @@
srcs: ["src/java/**/*.java"],
static_libs: [
"MicrodroidDeviceTestHelper",
+ "MicrodroidTestHelper",
"androidx.test.runner",
"androidx.test.ext.junit",
"com.android.microdroid.testservice-java",
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
index eb45a71..da08a47 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
@@ -22,7 +22,7 @@
import android.system.virtualmachine.VirtualMachine;
import android.util.Log;
-import com.android.microdroid.test.MicrodroidDeviceTestBase.VmEventListener;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase.VmEventListener;
import com.android.microdroid.testservice.IBenchmarkService;
/**
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 200b7bf..cdaf70c 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -31,7 +31,8 @@
import android.system.virtualmachine.VirtualMachineException;
import android.util.Log;
-import com.android.microdroid.test.MicrodroidDeviceTestBase;
+import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
import com.android.microdroid.testservice.IBenchmarkService;
import org.junit.Before;
@@ -46,6 +47,7 @@
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(Parameterized.class)
@@ -68,6 +70,8 @@
@Parameterized.Parameter public boolean mProtectedVm;
+ private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_NAME_PREFIX);
+
private Instrumentation mInstrumentation;
@Before
@@ -153,11 +157,11 @@
userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / nanoToMilli);
}
- reportMetrics(vmStartingTimeMetrics, "vm_starting_time_", "_ms");
- reportMetrics(bootTimeMetrics, "boot_time_", "_ms");
- reportMetrics(bootloaderTimeMetrics, "bootloader_time_", "_ms");
- reportMetrics(kernelBootTimeMetrics, "kernel_boot_time_", "_ms");
- reportMetrics(userspaceBootTimeMetrics, "userspace_boot_time_", "_ms");
+ reportMetrics(vmStartingTimeMetrics, "vm_starting_time", "ms");
+ reportMetrics(bootTimeMetrics, "boot_time", "ms");
+ reportMetrics(bootloaderTimeMetrics, "bootloader_time", "ms");
+ reportMetrics(kernelBootTimeMetrics, "kernel_boot_time", "ms");
+ reportMetrics(userspaceBootTimeMetrics, "userspace_boot_time", "ms");
}
@Test
@@ -187,7 +191,7 @@
mInner.newVmConfigBuilder("assets/vm_config_io.json")
.debugLevel(DebugLevel.FULL)
.build();
- List<Double> transferRates = new ArrayList<>();
+ List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
int port = (mProtectedVm ? 5666 : 6666) + i;
@@ -196,7 +200,7 @@
VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
BenchmarkVmListener.create(new VsockListener(transferRates, port)).runToFinish(TAG, vm);
}
- reportMetrics(transferRates, "vsock/transfer_host_to_vm_", "_mb_per_sec");
+ reportMetrics(transferRates, "vsock/transfer_host_to_vm", "mb_per_sec");
}
@Test
@@ -214,7 +218,7 @@
mInner.newVmConfigBuilder("assets/vm_config_io.json")
.debugLevel(DebugLevel.FULL)
.build();
- List<Double> readRates = new ArrayList<>();
+ List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT + 1; ++i) {
if (i == 1) {
@@ -230,33 +234,15 @@
.runToFinish(TAG, vm);
}
reportMetrics(
- readRates,
- isRand ? "virtio-blk/rand_read_" : "virtio-blk/seq_read_",
- "_mb_per_sec");
+ readRates, isRand ? "virtio-blk/rand_read" : "virtio-blk/seq_read", "mb_per_sec");
}
- private void reportMetrics(List<Double> data, String base, String suffix) {
- double sum = 0;
- double min = Double.MAX_VALUE;
- double max = Double.MIN_VALUE;
- for (double d : data) {
- sum += d;
- if (min > d) min = d;
- if (max < d) max = d;
- }
- double avg = sum / data.size();
- double sqSum = 0;
- for (double d : data) {
- sqSum += (d - avg) * (d - avg);
- }
- double stdDev = Math.sqrt(sqSum / (data.size() - 1));
-
+ private void reportMetrics(List<Double> metrics, String name, String unit) {
+ Map<String, Double> stats = mMetricsProcessor.computeStats(metrics, name, unit);
Bundle bundle = new Bundle();
- String prefix = METRIC_NAME_PREFIX + base;
- bundle.putDouble(prefix + "min" + suffix, min);
- bundle.putDouble(prefix + "max" + suffix, max);
- bundle.putDouble(prefix + "average" + suffix, avg);
- bundle.putDouble(prefix + "stdev" + suffix, stdDev);
+ for (Map.Entry<String, Double> entry : stats.entrySet()) {
+ bundle.putDouble(entry.getKey(), entry.getValue());
+ }
mInstrumentation.sendStatus(0, bundle);
}
diff --git a/tests/benchmark/src/jni/io_vsock_host_jni.cpp b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
index 7f3d655..dd32e29 100644
--- a/tests/benchmark/src/jni/io_vsock_host_jni.cpp
+++ b/tests/benchmark/src/jni/io_vsock_host_jni.cpp
@@ -36,7 +36,7 @@
clock_t end = clock();
double elapsed_seconds = (double)(end - start) / CLOCKS_PER_SEC;
LOG(INFO) << "Host:Finished sending data in " << elapsed_seconds << " seconds.";
- double send_rate = num_bytes_to_send / kNumBytesPerMB / elapsed_seconds;
+ double send_rate = (double)num_bytes_to_send / kNumBytesPerMB / elapsed_seconds;
return {send_rate};
}
diff --git a/tests/benchmark_hostside/Android.bp b/tests/benchmark_hostside/Android.bp
index 354a260..c5c7571 100644
--- a/tests/benchmark_hostside/Android.bp
+++ b/tests/benchmark_hostside/Android.bp
@@ -12,6 +12,7 @@
],
static_libs: [
"MicrodroidHostTestHelper",
+ "MicrodroidTestHelper",
],
test_suites: [
"general-tests",
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 8e9da55..ca4d1d0 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -16,8 +16,6 @@
package android.avf.test;
-import android.platform.test.annotations.RootPermissionTest;
-
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -25,8 +23,11 @@
import static org.junit.Assume.assumeTrue;
-import com.android.microdroid.test.CommandRunner;
-import com.android.microdroid.test.MicrodroidHostTestCaseBase;
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.util.CommandResult;
@@ -36,6 +37,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -61,6 +65,8 @@
private static final int ROUND_COUNT = 5;
private static final String METRIC_PREFIX = "avf_perf/hostside/";
+ private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_PREFIX);
+
@Before
public void setUp() throws Exception {
testIfDeviceIsCapable(getDevice());
@@ -80,11 +86,10 @@
@Test
public void testBootEnableAndDisablePKVM() throws Exception {
-
testPKVMStatusSwitchSupported();
- double[] bootWithPKVMEnableTime = new double[ROUND_COUNT];
- double[] bootWithoutPKVMEnableTime = new double[ROUND_COUNT];
+ List<Double> bootWithPKVMEnableTime = new ArrayList<>(ROUND_COUNT);
+ List<Double> bootWithoutPKVMEnableTime = new ArrayList<>(ROUND_COUNT);
for (int round = 0; round < ROUND_COUNT; ++round) {
@@ -93,7 +98,7 @@
rebootFromBootloaderAndWaitBootCompleted();
long elapsedWithPKVMEnable = System.nanoTime() - start;
double elapsedSec = elapsedWithPKVMEnable / NANOS_IN_SEC;
- bootWithPKVMEnableTime[round] = elapsedSec;
+ bootWithPKVMEnableTime.add(elapsedSec);
CLog.i("Boot time with PKVM enable took " + elapsedSec + "s");
setPKVMStatusWithRebootToBootloader(false);
@@ -101,45 +106,47 @@
rebootFromBootloaderAndWaitBootCompleted();
long elapsedWithoutPKVMEnable = System.nanoTime() - start;
elapsedSec = elapsedWithoutPKVMEnable / NANOS_IN_SEC;
- bootWithoutPKVMEnableTime[round] = elapsedSec;
+ bootWithoutPKVMEnableTime.add(elapsedSec);
CLog.i("Boot time with PKVM disable took " + elapsedSec + "s");
}
- reportMetric("boot_time_with_pkvm_enable", "s", bootWithPKVMEnableTime);
- reportMetric("boot_time_with_pkvm_disable", "s", bootWithoutPKVMEnableTime);
+ reportMetric(bootWithPKVMEnableTime, "boot_time_with_pkvm_enable", "s");
+ reportMetric(bootWithoutPKVMEnableTime, "boot_time_with_pkvm_disable", "s");
}
@Test
public void testBootWithAndWithoutCompOS() throws Exception {
assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
- double[] bootWithCompOsTime = new double[ROUND_COUNT];
- double[] bootWithoutCompOsTime = new double[ROUND_COUNT];
+ List<Double> bootWithCompOsTime = new ArrayList<>(ROUND_COUNT);
+ List<Double> bootWithoutCompOsTime = new ArrayList<>(ROUND_COUNT);
for (int round = 0; round < ROUND_COUNT; ++round) {
// Boot time with compilation OS test.
reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC);
+ getDevice().nonBlockingReboot();
long start = System.nanoTime();
- rebootAndWaitBootCompleted();
+ waitForBootCompleted();
long elapsedWithCompOS = System.nanoTime() - start;
double elapsedSec = elapsedWithCompOS / NANOS_IN_SEC;
- bootWithCompOsTime[round] = elapsedSec;
+ bootWithCompOsTime.add(elapsedSec);
CLog.i("Boot time with compilation OS took " + elapsedSec + "s");
// Boot time without compilation OS test.
reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
+ getDevice().nonBlockingReboot();
start = System.nanoTime();
- rebootAndWaitBootCompleted();
+ waitForBootCompleted();
long elapsedWithoutCompOS = System.nanoTime() - start;
elapsedSec = elapsedWithoutCompOS / NANOS_IN_SEC;
- bootWithoutCompOsTime[round] = elapsedSec;
+ bootWithoutCompOsTime.add(elapsedSec);
CLog.i("Boot time without compilation OS took " + elapsedSec + "s");
}
- reportMetric("boot_time_with_compos", "s", bootWithCompOsTime);
- reportMetric("boot_time_without_compos", "s", bootWithoutCompOsTime);
+ reportMetric(bootWithCompOsTime, "boot_time_with_compos", "s");
+ reportMetric(bootWithoutCompOsTime, "boot_time_without_compos", "s");
}
private void testPKVMStatusSwitchSupported() throws Exception {
@@ -154,31 +161,12 @@
assumeTrue(!result.getStderr().contains("Invalid oem command"));
}
- private void reportMetric(String name, String unit, double[] values) {
- double sum = 0;
- double min = Double.MAX_VALUE;
- double max = Double.MIN_VALUE;
-
- for (double val : values) {
- sum += val;
- min = val < min ? val : min;
- max = val > max ? val : max;
- }
-
- double average = sum / values.length;
-
- double variance = 0;
- for (double val : values) {
- final double tmp = val - average;
- variance += tmp * tmp;
- }
- double stdev = Math.sqrt(variance / (double) (values.length - 1));
-
+ private void reportMetric(List<Double> data, String name, String unit) {
+ Map<String, Double> stats = mMetricsProcessor.computeStats(data, name, unit);
TestMetrics metrics = new TestMetrics();
- metrics.addTestMetric(METRIC_PREFIX + name + "_average_" + unit, Double.toString(average));
- metrics.addTestMetric(METRIC_PREFIX + name + "_min_" + unit, Double.toString(min));
- metrics.addTestMetric(METRIC_PREFIX + name + "_max_" + unit, Double.toString(max));
- metrics.addTestMetric(METRIC_PREFIX + name + "_stdev_" + unit, Double.toString(stdev));
+ for (Map.Entry<String, Double> entry : stats.entrySet()) {
+ metrics.addTestMetric(entry.getKey(), Double.toString(entry.getValue()));
+ }
}
private void setPKVMStatusWithRebootToBootloader(boolean isEnable) throws Exception {
@@ -207,6 +195,11 @@
}
assertWithMessage("Failed to set PKVM status. Reason: " + result)
.that(result.toString()).ignoringCase().contains(expectedOutput);
+
+ // Skip the test if running on a build with pkvm_enabler. Disabling
+ // pKVM for such build results in a bootloop.
+ assertWithMessage("Expected build with PKVM status misc=auto. Reason: " + result)
+ .that(result.toString()).ignoringCase().contains("misc=auto");
}
private void rebootFromBootloaderAndWaitBootCompleted() throws Exception {
@@ -216,8 +209,7 @@
getDevice().enableAdbRoot();
}
- private void rebootAndWaitBootCompleted() throws Exception {
- getDevice().nonBlockingReboot();
+ private void waitForBootCompleted() throws Exception {
getDevice().waitForDeviceOnline(BOOT_COMPLETE_TIMEOUT_MS);
getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS);
getDevice().enableAdbRoot();
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index f8dcef7..f77dae5 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -9,8 +9,14 @@
}
java_library_static {
+ name: "MicrodroidTestHelper",
+ srcs: ["src/java/com/android/microdroid/test/common/*.java"],
+ host_supported: true,
+}
+
+java_library_static {
name: "MicrodroidDeviceTestHelper",
- srcs: ["src/java/com/android/microdroid/**/*.java"],
+ srcs: ["src/java/com/android/microdroid/test/device/*.java"],
static_libs: [
"androidx.test.runner",
"androidx.test.ext.junit",
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
new file mode 100644
index 0000000..c12b07d
--- /dev/null
+++ b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid.test.common;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** This class processes the metrics for both device tests and host tests. */
+public final class MetricsProcessor {
+ private final String mPrefix;
+
+ public MetricsProcessor(String prefix) {
+ mPrefix = prefix;
+ }
+
+ /**
+ * Computes the min, max, average and standard deviation of the given metrics and saves them in
+ * a {@link Map} with the corresponding keys equal to [mPrefix + name +
+ * _[min|max|average|stdev]_ + unit].
+ */
+ public Map<String, Double> computeStats(List<Double> metrics, String name, String unit) {
+ double sum = 0;
+ double min = Double.MAX_VALUE;
+ double max = Double.MIN_VALUE;
+ for (double d : metrics) {
+ sum += d;
+ if (min > d) min = d;
+ if (max < d) max = d;
+ }
+ double avg = sum / metrics.size();
+ double sqSum = 0;
+ for (double d : metrics) {
+ sqSum += (d - avg) * (d - avg);
+ }
+ double stdDev = Math.sqrt(sqSum / (metrics.size() - 1));
+
+ Map<String, Double> stats = new HashMap<String, Double>();
+ String prefix = mPrefix + name;
+ stats.put(prefix + "_min_" + unit, min);
+ stats.put(prefix + "_max_" + unit, max);
+ stats.put(prefix + "_average_" + unit, avg);
+ stats.put(prefix + "_stdev_" + unit, stdDev);
+ return stats;
+ }
+}
diff --git a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
similarity index 94%
rename from tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
rename to tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index a80111f..e5bc45a 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.microdroid.test;
+package com.android.microdroid.test.device;
import static com.google.common.truth.TruthJUnit.assume;
@@ -22,7 +22,6 @@
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
-import android.sysprop.HypervisorProperties;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
@@ -106,18 +105,29 @@
return;
}
if (protectedVm) {
- assume().withMessage("Skip where protected VMs aren't support")
- .that(HypervisorProperties.hypervisor_protected_vm_supported().orElse(false))
+ assume().withMessage("Skip where protected VMs aren't supported")
+ .that(hypervisor_protected_vm_supported())
.isTrue();
} else {
- assume().withMessage("Skip where VMs aren't support")
- .that(HypervisorProperties.hypervisor_vm_supported().orElse(false))
+ assume().withMessage("Skip where VMs aren't supported")
+ .that(hypervisor_vm_supported())
.isTrue();
}
Context context = ApplicationProvider.getApplicationContext();
mInner = new Inner(context, protectedVm, VirtualMachineManager.getInstance(context));
}
+ // These are inlined from android.sysprop.HypervisorProperties which isn't @SystemApi.
+ // TODO(b/243642678): Move to using a proper Java API for this.
+
+ private boolean hypervisor_vm_supported() {
+ return SystemProperties.getBoolean("ro.boot.hypervisor.vm.supported", false);
+ }
+
+ private boolean hypervisor_protected_vm_supported() {
+ return SystemProperties.getBoolean("ro.boot.hypervisor.protected_vm.supported", false);
+ }
+
public abstract static class VmEventListener implements VirtualMachineCallback {
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty();
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
similarity index 98%
rename from tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
rename to tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
index 2271325..2e9d078 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandResultSubject.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.microdroid.test;
+package com.android.microdroid.test.host;
import static com.google.common.truth.Truth.assertAbout;
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
similarity index 98%
rename from tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
rename to tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
index 2f9b3df..846531d 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/CommandRunner.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.microdroid.test;
+package com.android.microdroid.test.host;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java b/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
similarity index 97%
rename from tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
rename to tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
index be638ab..96ab543 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/LogArchiver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.microdroid.test;
+package com.android.microdroid.test.host;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
similarity index 98%
rename from tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
rename to tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index c84513a..cff06d5 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.microdroid.test;
+package com.android.microdroid.test.host;
-import static com.android.microdroid.test.CommandResultSubject.assertThat;
-import static com.android.microdroid.test.CommandResultSubject.command_results;
+import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.host.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertWithMessage;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index 0f461aa..1beee45 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -16,7 +16,7 @@
package com.android.microdroid.test;
-import static com.android.microdroid.test.CommandResultSubject.command_results;
+import static com.android.microdroid.test.host.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +38,8 @@
import android.cts.statsdatom.lib.ReportUtils;
import com.android.compatibility.common.util.CddTest;
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
import com.android.os.AtomsProto;
import com.android.os.StatsLog;
import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 325ebdb..47116eb 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -28,7 +28,6 @@
min_sdk_version: "33",
}
-// TODO(jiyong): make this a binary, not a shared library
cc_library_shared {
name: "MicrodroidTestNativeLib",
srcs: ["src/native/testbinary.cpp"],
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 5f34cff..0c048b9 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -31,6 +31,7 @@
import android.util.Log;
import com.android.compatibility.common.util.CddTest;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
import com.android.microdroid.testservice.ITestService;
import org.junit.Before;
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 473cbd5..6a4cc93 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -146,7 +146,7 @@
let size = size.try_into().map_err(|e| {
Status::new_exception_str(
ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("Invalid size {}: {}", size, e)),
+ Some(format!("Invalid size {}: {:?}", size, e)),
)
})?;
let image = clone_file(image_fd)?;
@@ -154,13 +154,13 @@
image.set_len(0).map_err(|e| {
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to reset a file: {}", e)),
+ Some(format!("Failed to reset a file: {:?}", e)),
)
})?;
let mut part = QcowFile::new(image, size).map_err(|e| {
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to create QCOW2 image: {}", e)),
+ Some(format!("Failed to create QCOW2 image: {:?}", e)),
)
})?;
@@ -175,7 +175,7 @@
.map_err(|e| {
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to initialize partition as {:?}: {}", partition_type, e)),
+ Some(format!("Failed to initialize partition as {:?}: {:?}", partition_type, e)),
)
})?;
@@ -251,7 +251,7 @@
for incoming_stream in listener.incoming() {
let mut incoming_stream = match incoming_stream {
Err(e) => {
- warn!("invalid incoming connection: {}", e);
+ warn!("invalid incoming connection: {:?}", e);
continue;
}
Ok(s) => s,
@@ -305,7 +305,7 @@
std::thread::spawn(|| {
if let Err(e) = handle_stream_connection_tombstoned() {
- warn!("Error receiving tombstone from guest or writing them. Error: {}", e);
+ warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
}
});
@@ -366,7 +366,7 @@
Status::new_service_specific_error_str(
-1,
Some(format!(
- "Failed to create temporary directory {:?} for VM files: {}",
+ "Failed to create temporary directory {:?} for VM files: {:?}",
temporary_directory, e
)),
)
@@ -382,7 +382,7 @@
Status::new_service_specific_error_str(
-1,
Some(format!(
- "Failed to load app config from {}: {}",
+ "Failed to load app config from {}: {:?}",
&config.configPath, e
)),
)
@@ -395,29 +395,28 @@
// Check if partition images are labeled incorrectly. This is to prevent random images
// which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
- // being loaded in a pVM. Specifically, for images in the raw config, nothing is allowed
- // to be labeled as app_data_file. For images in the app config, nothing but the instance
- // partition is allowed to be labeled as such.
+ // being loaded in a pVM. This applies to everything in the raw config, and everything but
+ // the non-executable, generated partitions in the app config.
config
.disks
.iter()
.flat_map(|disk| disk.partitions.iter())
.filter(|partition| {
if is_app_config {
- partition.label != "vm-instance"
+ !is_safe_app_partition(&partition.label)
} else {
true // all partitions are checked
}
})
.try_for_each(check_label_for_partition)
- .map_err(|e| Status::new_service_specific_error_str(-1, Some(e.to_string())))?;
+ .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
let zero_filler_path = temporary_directory.join("zero.img");
write_zero_filler(&zero_filler_path).map_err(|e| {
error!("Failed to make composite image: {:?}", e);
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to make composite image: {}", e)),
+ Some(format!("Failed to make composite image: {:?}", e)),
)
})?;
@@ -442,10 +441,10 @@
// will be sent back to the client (i.e. the VM owner) for readout.
let ramdump_path = temporary_directory.join("ramdump");
let ramdump = prepare_ramdump_file(&ramdump_path).map_err(|e| {
- error!("Failed to prepare ramdump file: {}", e);
+ error!("Failed to prepare ramdump file: {:?}", e);
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to prepare ramdump file: {}", e)),
+ Some(format!("Failed to prepare ramdump file: {:?}", e)),
)
})?;
@@ -482,7 +481,7 @@
error!("Failed to create VM with config {:?}: {:?}", config, e);
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to create VM: {}", e)),
+ Some(format!("Failed to create VM: {:?}", e)),
)
})?,
);
@@ -499,7 +498,7 @@
for stream in listener.incoming() {
let stream = match stream {
Err(e) => {
- warn!("invalid incoming connection: {}", e);
+ warn!("invalid incoming connection: {:?}", e);
continue;
}
Ok(s) => s,
@@ -572,7 +571,7 @@
error!("Failed to make composite image with config {:?}: {:?}", disk, e);
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to make composite image: {}", e)),
+ Some(format!("Failed to make composite image: {:?}", e)),
)
})?;
@@ -730,10 +729,33 @@
/// Check if a partition has selinux labels that are not allowed
fn check_label_for_partition(partition: &Partition) -> Result<()> {
let ctx = getfilecon(partition.image.as_ref().unwrap().as_ref())?;
- if ctx == SeContext::new("u:object_r:app_data_file:s0").unwrap() {
- Err(anyhow!("Partition {} shouldn't be labeled as {}", &partition.label, ctx))
- } else {
- Ok(())
+ check_label_is_allowed(&ctx).with_context(|| format!("Partition {} invalid", &partition.label))
+}
+
+// Return whether a partition is exempt from selinux label checks, because we know that it does
+// not contain code and is likely to be generated in an app-writable directory.
+fn is_safe_app_partition(label: &str) -> bool {
+ // See make_payload_disk in payload.rs.
+ label == "vm-instance"
+ || label == "microdroid-apk-idsig"
+ || label == "payload-metadata"
+ || label.starts_with("extra-idsig-")
+}
+
+fn check_label_is_allowed(ctx: &SeContext) -> Result<()> {
+ // We only want to allow code in a VM payload to be sourced from places that apps, and the
+ // system, do not have write access to.
+ // (Note that sepolicy must also grant read access for these types to both virtualization
+ // service and crosvm.)
+ // App private data files are deliberately excluded, to avoid arbitrary payloads being run on
+ // user devices (W^X).
+ match ctx.selinux_type()? {
+ | "system_file" // immutable dm-verity protected partition
+ | "apk_data_file" // APKs of an installed app
+ | "staging_data_file" // updated/staged APEX imagess
+ | "shell_data_file" // test files created via adb shell
+ => Ok(()),
+ _ => bail!("Label {} is not allowed", ctx),
}
}
@@ -802,7 +824,7 @@
VsockStream::connect_with_cid_port(self.instance.cid, port as u32).map_err(|e| {
Status::new_service_specific_error_str(
-1,
- Some(format!("Failed to connect: {}", e)),
+ Some(format!("Failed to connect: {:?}", e)),
)
})?;
Ok(vsock_stream_to_pfd(stream))
@@ -881,7 +903,7 @@
let pfd = ParcelFileDescriptor::new(ramdump);
for callback in callbacks {
if let Err(e) = callback.onRamdump(cid as i32, &pfd) {
- error!("Error notifying ramdump of VM CID {}: {}", cid, e);
+ error!("Error notifying ramdump of VM CID {}: {:?}", cid, e);
}
}
}
@@ -983,7 +1005,7 @@
file.as_ref().try_clone().map_err(|e| {
Status::new_exception_str(
ExceptionCode::BAD_PARCELABLE,
- Some(format!("Failed to clone File from ParcelFileDescriptor: {}", e)),
+ Some(format!("Failed to clone File from ParcelFileDescriptor: {:?}", e)),
)
})
}
@@ -1005,7 +1027,7 @@
VersionReq::parse(s).map_err(|e| {
Status::new_exception_str(
ExceptionCode::BAD_PARCELABLE,
- Some(format!("Invalid platform version requirement {}: {}", s, e)),
+ Some(format!("Invalid platform version requirement {}: {:?}", s, e)),
)
})
}
@@ -1132,3 +1154,33 @@
)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_is_allowed_label_for_partition() -> Result<()> {
+ let expected_results = vec![
+ ("u:object_r:system_file:s0", true),
+ ("u:object_r:apk_data_file:s0", true),
+ ("u:object_r:app_data_file:s0", false),
+ ("u:object_r:app_data_file:s0:c512,c768", false),
+ ("u:object_r:privapp_data_file:s0:c512,c768", false),
+ ("invalid", false),
+ ("user:role:apk_data_file:severity:categories", true),
+ ("user:role:apk_data_file:severity:categories:extraneous", false),
+ ];
+
+ for (label, expected_valid) in expected_results {
+ let context = SeContext::new(label)?;
+ let result = check_label_is_allowed(&context);
+ if expected_valid {
+ assert!(result.is_ok(), "Expected label {} to be allowed, got {:?}", label, result);
+ } else if result.is_ok() {
+ bail!("Expected label {} to be disallowed", label);
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 93a5966..cb10eff 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -72,12 +72,3 @@
}
Ok(())
}
-
-#[cfg(test)]
-mod tests {
- /// We need to have at least one test to avoid errors running the test suite, so this is a
- /// placeholder until we have real tests.
- #[test]
- #[ignore]
- fn placeholder() {}
-}
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 42c51a1..7807cd6 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -201,7 +201,7 @@
}
/// Creates a DiskImage with partitions:
-/// metadata: metadata
+/// payload-metadata: metadata
/// microdroid-apex-0: apex 0
/// microdroid-apex-1: apex 1
/// ..
diff --git a/virtualizationservice/src/selinux.rs b/virtualizationservice/src/selinux.rs
index e450dee..0485943 100644
--- a/virtualizationservice/src/selinux.rs
+++ b/virtualizationservice/src/selinux.rs
@@ -14,7 +14,7 @@
//! Wrapper to libselinux
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
use std::ffi::{CStr, CString};
use std::fmt;
use std::fs::File;
@@ -30,6 +30,7 @@
/// `freecon` to free the resources when dropped. In its second variant it stores
/// an `std::ffi::CString` that can be initialized from a Rust string slice.
#[derive(Debug)]
+#[allow(dead_code)] // CString variant is used in tests
pub enum SeContext {
/// Wraps a raw context c-string as returned by libselinux.
Raw(*mut ::std::os::raw::c_char),
@@ -78,12 +79,27 @@
impl SeContext {
/// Initializes the `SeContext::CString` variant from a Rust string slice.
+ #[allow(dead_code)] // Used in tests
pub fn new(con: &str) -> Result<Self> {
Ok(Self::CString(
CString::new(con)
.with_context(|| format!("Failed to create SeContext with \"{}\"", con))?,
))
}
+
+ pub fn selinux_type(&self) -> Result<&str> {
+ let context = self.deref().to_str().context("Label is not valid UTF8")?;
+
+ // The syntax is user:role:type:sensitivity[:category,...],
+ // ignoring security level ranges, which don't occur on Android. See
+ // https://github.com/SELinuxProject/selinux-notebook/blob/main/src/security_context.md
+ // We only want the type.
+ let fields: Vec<_> = context.split(':').collect();
+ if fields.len() < 4 || fields.len() > 5 {
+ bail!("Syntactically invalid label {}", self);
+ }
+ Ok(fields[2])
+ }
}
pub fn getfilecon(file: &File) -> Result<SeContext> {