summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/tests/bugreports/Android.bp27
-rw-r--r--core/tests/bugreports/AndroidManifest.xml24
-rw-r--r--core/tests/bugreports/AndroidTest.xml35
-rw-r--r--core/tests/bugreports/config/test-sysconfig.xml20
-rwxr-xr-xcore/tests/bugreports/run.sh61
-rw-r--r--core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java341
6 files changed, 508 insertions, 0 deletions
diff --git a/core/tests/bugreports/Android.bp b/core/tests/bugreports/Android.bp
new file mode 100644
index 000000000000..d3bf0dd7a7e8
--- /dev/null
+++ b/core/tests/bugreports/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 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.
+
+android_test {
+ name: "BugreportManagerTestCases",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: ["androidx.test.rules", "truth-prebuilt"],
+ test_suites: ["general-tests"],
+ sdk_version: "test_current",
+ platform_apis: true,
+}
+
diff --git a/core/tests/bugreports/AndroidManifest.xml b/core/tests/bugreports/AndroidManifest.xml
new file mode 100644
index 000000000000..0cfb8747e629
--- /dev/null
+++ b/core/tests/bugreports/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.os.bugreports.tests">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.os.bugreports.tests"
+ android:label="Unit tests of BugreportManager" />
+</manifest>
diff --git a/core/tests/bugreports/AndroidTest.xml b/core/tests/bugreports/AndroidTest.xml
new file mode 100644
index 000000000000..410ca6043583
--- /dev/null
+++ b/core/tests/bugreports/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for BugreportManager test cases">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="BugreportManagerTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.os.bugreports.tests"/>
+ <!-- test-timeout unit is ms, value = 30 min -->
+ <option name="test-timeout" value="1800000" />
+ <option name="runtime-hint" value="30m" />
+ </test>
+</configuration>
diff --git a/core/tests/bugreports/config/test-sysconfig.xml b/core/tests/bugreports/config/test-sysconfig.xml
new file mode 100644
index 000000000000..09c69ba699de
--- /dev/null
+++ b/core/tests/bugreports/config/test-sysconfig.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<!-- WARNING: This is a test config. -->
+<config>
+ <bugreport-whitelisted package="com.android.os.bugreports.tests" />
+</config>
diff --git a/core/tests/bugreports/run.sh b/core/tests/bugreports/run.sh
new file mode 100755
index 000000000000..010339836538
--- /dev/null
+++ b/core/tests/bugreports/run.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+# Copyright (C) 2019 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.
+
+# Script to run bugreport unitests
+# Must run on a rooted device.
+# Must run lunch before running the script
+# Usage: ${ANDROID_BUILD_TOP}/frameworks/base/core/tests/bugreports/run.sh
+
+# NOTE: This script replaces the framework-sysconfig.xml on your device, so use with caution.
+# It tries to replace it when done, but if the script does not finish cleanly
+# (for e.g. force stopped mid-way) your device will be left in an inconsistent state.
+# Reflashing will restore the right config.
+
+TMP_SYS_CONFIG=/var/tmp/framework-sysconfig.xml
+
+if [[ -z $ANDROID_PRODUCT_OUT ]]; then
+ echo "Please lunch before running this test."
+ exit 0
+fi
+
+# Print every command to console.
+set -x
+
+make -j BugreportManagerTestCases &&
+ adb root &&
+ adb remount &&
+ adb wait-for-device &&
+ # Save the sysconfig file in a tmp location and push the test config in
+ adb pull /system/etc/sysconfig/framework-sysconfig.xml "${TMP_SYS_CONFIG}" &&
+ adb push $ANDROID_BUILD_TOP/frameworks/base/core/tests/bugreports/config/test-sysconfig.xml /system/etc/sysconfig/framework-sysconfig.xml &&
+ # The test app needs to be a priv-app.
+ adb push $OUT/testcases/BugreportManagerTestCases/*/BugreportManagerTestCases.apk /system/priv-app ||
+ exit 1
+
+adb reboot &&
+adb wait-for-device &&
+atest BugreportManagerTest || echo "Tests FAILED!"
+
+# Restore the saved config file
+if [ -f "${TMP_SYS_CONFIG}" ]; then
+ SIZE=$(stat --printf="%s" "${TMP_SYS_CONFIG}")
+ if [ SIZE > 0 ]; then
+ adb remount &&
+ adb wait-for-device &&
+ adb push "${TMP_SYS_CONFIG}" /system/etc/sysconfig/framework-sysconfig.xml &&
+ rm "${TMP_SYS_CONFIG}"
+ fi
+fi
diff --git a/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java b/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java
new file mode 100644
index 000000000000..220f854c337a
--- /dev/null
+++ b/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 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.os.bugreports.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.BugreportManager;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Tests for BugreportManager API.
+ */
+@RunWith(JUnit4.class)
+public class BugreportManagerTest {
+ @Rule public TestName name = new TestName();
+
+ private static final String TAG = "BugreportManagerTest";
+ private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
+
+ private Handler mHandler;
+ private Executor mExecutor;
+ private BugreportManager mBrm;
+ private ParcelFileDescriptor mBugreportFd;
+ private ParcelFileDescriptor mScreenshotFd;
+
+ @Before
+ public void setup() throws Exception {
+ mHandler = createHandler();
+ mExecutor = (runnable) -> {
+ if (mHandler != null) {
+ mHandler.post(() -> {
+ runnable.run();
+ });
+ }
+ };
+
+ mBrm = getBugreportManager();
+ mBugreportFd = parcelFd("bugreport_" + name.getMethodName(), ".zip");
+ mScreenshotFd = parcelFd("screenshot_" + name.getMethodName(), ".png");
+
+ getPermissions();
+ }
+
+ @After
+ public void teardown() throws Exception {
+ dropPermissions();
+ }
+
+
+ @Test
+ public void normalFlow_wifi() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+ waitTillDoneOrTimeout(callback);
+
+ assertThat(callback.isDone()).isTrue();
+ // Wifi bugreports should not receive any progress.
+ assertThat(callback.hasReceivedProgress()).isFalse();
+ // TODO: Because of b/130234145, consent dialog is not shown; so we get a timeout error.
+ // When the bug is fixed, accept consent via UIAutomator and verify contents
+ // of mBugreportFd.
+ assertThat(callback.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ @Test
+ public void normalFlow_interactive() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd, interactive(), mExecutor, callback);
+
+ waitTillDoneOrTimeout(callback);
+ assertThat(callback.isDone()).isTrue();
+ // Interactive bugreports show progress updates.
+ assertThat(callback.hasReceivedProgress()).isTrue();
+ assertThat(callback.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ @Test
+ public void simultaneousBugreportsNotAllowed() throws Exception {
+ // Start bugreport #1
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+
+ // Before #1 is done, try to start #2.
+ assertThat(callback.isDone()).isFalse();
+ BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
+ ParcelFileDescriptor bugreportFd2 = parcelFd("bugreport_2_" + name.getMethodName(), ".zip");
+ ParcelFileDescriptor screenshotFd2 =
+ parcelFd("screenshot_2_" + name.getMethodName(), ".png");
+ mBrm.startBugreport(bugreportFd2, screenshotFd2, wifi(), mExecutor, callback2);
+ Thread.sleep(500 /* .5s */);
+
+ // Verify #2 encounters an error.
+ assertThat(callback2.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ assertFdsAreClosed(bugreportFd2, screenshotFd2);
+
+ // Cancel #1 so we can move on to the next test.
+ mBrm.cancelBugreport();
+ Thread.sleep(500 /* .5s */);
+ assertThat(callback.isDone()).isTrue();
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ @Test
+ public void cancelBugreport() throws Exception {
+ // Start a bugreport.
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+
+ // Verify it's not finished yet.
+ assertThat(callback.isDone()).isFalse();
+
+ // Try to cancel it, but first without DUMP permission.
+ dropPermissions();
+ try {
+ mBrm.cancelBugreport();
+ fail("Expected cancelBugreport to throw SecurityException without DUMP permission");
+ } catch (SecurityException expected) {
+ }
+ assertThat(callback.isDone()).isFalse();
+
+ // Try again, with DUMP permission.
+ getPermissions();
+ mBrm.cancelBugreport();
+ Thread.sleep(500 /* .5s */);
+ assertThat(callback.isDone()).isTrue();
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ @Test
+ public void insufficientPermissions_throwsException() throws Exception {
+ dropPermissions();
+
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ try {
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+ fail("Expected startBugreport to throw SecurityException without DUMP permission");
+ } catch (SecurityException expected) {
+ }
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ @Test
+ public void invalidBugreportMode_throwsException() throws Exception {
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+ try {
+ mBrm.startBugreport(mBugreportFd, mScreenshotFd,
+ new BugreportParams(25) /* unknown bugreport mode */, mExecutor, callback);
+ fail("Expected to throw IllegalArgumentException with unknown bugreport mode");
+ } catch (IllegalArgumentException expected) {
+ }
+ assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+ }
+
+ private Handler createHandler() {
+ HandlerThread handlerThread = new HandlerThread("BugreportManagerTest");
+ handlerThread.start();
+ return new Handler(handlerThread.getLooper());
+ }
+
+ /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
+ private static final class BugreportCallbackImpl extends BugreportCallback {
+ private int mErrorCode = -1;
+ private boolean mSuccess = false;
+ private boolean mReceivedProgress = false;
+ private final Object mLock = new Object();
+
+ @Override
+ public void onProgress(float progress) {
+ synchronized (mLock) {
+ mReceivedProgress = true;
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ synchronized (mLock) {
+ mErrorCode = errorCode;
+ Log.d(TAG, "bugreport errored.");
+ }
+ }
+
+ @Override
+ public void onFinished() {
+ synchronized (mLock) {
+ Log.d(TAG, "bugreport finished.");
+ mSuccess = true;
+ }
+ }
+
+ /* Indicates completion; and ended up with a success or error. */
+ public boolean isDone() {
+ synchronized (mLock) {
+ return (mErrorCode != -1) || mSuccess;
+ }
+ }
+
+ public int getErrorCode() {
+ synchronized (mLock) {
+ return mErrorCode;
+ }
+ }
+
+ public boolean isSuccess() {
+ synchronized (mLock) {
+ return mSuccess;
+ }
+ }
+
+ public boolean hasReceivedProgress() {
+ synchronized (mLock) {
+ return mReceivedProgress;
+ }
+ }
+ }
+
+ public static BugreportManager getBugreportManager() {
+ Context context = InstrumentationRegistry.getContext();
+ BugreportManager bm =
+ (BugreportManager) context.getSystemService(Context.BUGREPORT_SERVICE);
+ if (bm == null) {
+ throw new AssertionError("Failed to get BugreportManager");
+ }
+ return bm;
+ }
+
+ private static ParcelFileDescriptor parcelFd(String prefix, String extension) throws Exception {
+ File f = File.createTempFile(prefix, extension);
+ f.setReadable(true, true);
+ f.setWritable(true, true);
+
+ return ParcelFileDescriptor.open(f,
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+ }
+
+ private static void dropPermissions() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ private static void getPermissions() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.DUMP);
+ }
+
+ private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
+ try {
+ int fd = pfd.getFd();
+ fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private static void assertFdsAreClosed(ParcelFileDescriptor... pfds) {
+ for (int i = 0; i < pfds.length; i++) {
+ assertFdIsClosed(pfds[i]);
+ }
+ }
+
+ private static long now() {
+ return System.currentTimeMillis();
+ }
+
+ private static boolean shouldTimeout(long startTimeMs) {
+ return now() - startTimeMs >= BUGREPORT_TIMEOUT_MS;
+ }
+
+ private static void waitTillDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
+ long startTimeMs = now();
+ while (!callback.isDone()) {
+ Thread.sleep(1000 /* 1s */);
+ if (shouldTimeout(startTimeMs)) {
+ break;
+ }
+ Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms");
+ }
+ }
+
+ /*
+ * Returns a {@link BugreportParams} for wifi only bugreport.
+ *
+ * <p>Wifi bugreports have minimal content and are fast to run. They also suppress progress
+ * updates.
+ */
+ private static BugreportParams wifi() {
+ return new BugreportParams(BugreportParams.BUGREPORT_MODE_WIFI);
+ }
+
+ /*
+ * Returns a {@link BugreportParams} for interactive bugreport that offers progress updates.
+ *
+ * <p>This is the typical bugreport taken by users. This can take on the order of minutes to
+ * finish.
+ */
+ private static BugreportParams interactive() {
+ return new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+ }
+}