summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-08-05 01:04:01 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-08-05 01:04:01 +0000
commit24ae90ee21bc9aac4d771128d5e0abf239d07b82 (patch)
treec8b61c06d35e6c6f3929da084bd3ba19af14ff7d
parentbe5d76dbd32e1730f182eee99aa0ab2d7e0ab474 (diff)
parentcec00c1e6f26c3ac7fcbb18ff94beafde587a008 (diff)
downloadplatform_testing-24ae90ee21bc9aac4d771128d5e0abf239d07b82.tar.gz
Change-Id: I7a7683908c065890d54e4c8c71953f5995c17f10
-rw-r--r--libraries/collectors-helper/simpleperf/Android.bp29
-rw-r--r--libraries/collectors-helper/simpleperf/src/com/android/helpers/SimpleperfHelper.java204
-rw-r--r--libraries/collectors-helper/simpleperf/test/Android.bp30
-rw-r--r--libraries/collectors-helper/simpleperf/test/src/com/android/helpers/tests/SimpleperfHelperTest.java95
-rw-r--r--libraries/device-collectors/src/main/Android.bp1
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/SimpleperfListener.java207
-rw-r--r--libraries/device-collectors/src/test/java/android/device/collectors/SimpleperfListenerTest.java251
-rw-r--r--libraries/health/runners/longevity/host/src/android/host/test/longevity/listener/TimeoutTerminator.java2
-rw-r--r--libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/LongevitySuiteTest.java40
-rw-r--r--libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/listener/TimeoutTerminatorTest.java4
-rw-r--r--libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevityClassRunner.java7
-rw-r--r--libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevitySuite.java6
-rw-r--r--libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/listener/TimeoutTerminatorTest.java4
13 files changed, 866 insertions, 14 deletions
diff --git a/libraries/collectors-helper/simpleperf/Android.bp b/libraries/collectors-helper/simpleperf/Android.bp
new file mode 100644
index 000000000..98d66b039
--- /dev/null
+++ b/libraries/collectors-helper/simpleperf/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 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.
+
+// Used for collecting simpleperf samples.
+java_library {
+ name: "simpleperf-helper",
+ defaults: ["tradefed_errorprone_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.runner",
+ "ub-uiautomator",
+ ],
+
+ sdk_version: "current",
+}
+
diff --git a/libraries/collectors-helper/simpleperf/src/com/android/helpers/SimpleperfHelper.java b/libraries/collectors-helper/simpleperf/src/com/android/helpers/SimpleperfHelper.java
new file mode 100644
index 000000000..2e08f65b7
--- /dev/null
+++ b/libraries/collectors-helper/simpleperf/src/com/android/helpers/SimpleperfHelper.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 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.helpers;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+
+import java.io.IOException;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * SimpleperfHelper is used to start and stop simpleperf sample collection and move the output
+ * sample file to the destination folder.
+ */
+public class SimpleperfHelper {
+
+ private static final String LOG_TAG = SimpleperfHelper.class.getSimpleName();
+ private static final String SIMPLEPERF_TMP_FILE_PATH = "/data/local/tmp/perf.data";
+
+ private static final String SIMPLEPERF_START_CMD =
+ "simpleperf record -o %s -g --post-unwind=yes -f 500 -a --exclude-perf";
+ private static final String SIMPLEPERF_STOP_CMD = "pkill -INT simpleperf";
+ private static final String SIMPLEPERF_PROC_ID_CMD = "pidof simpleperf";
+ private static final String REMOVE_CMD = "rm %s";
+ private static final String MOVE_CMD = "mv %s %s";
+
+ private static final int SIMPLEPERF_STOP_WAIT_COUNT = 12;
+ private static final long SIMPLEPERF_STOP_WAIT_TIME = 5000;
+
+ private UiDevice mUiDevice;
+
+ public boolean startCollecting() {
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ try {
+ // Cleanup any running simpleperf sessions.
+ Log.i(LOG_TAG, "Cleanup simpleperf before starting.");
+ if (isSimpleperfRunning()) {
+ Log.i(LOG_TAG, "Simpleperf is already running. Stopping simpleperf.");
+ if (!stopSimpleperf()) {
+ return false;
+ }
+ }
+
+ Log.i(LOG_TAG, String.format("Starting simpleperf"));
+ new Thread() {
+ @Override
+ public void run() {
+ UiDevice uiDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ try {
+ uiDevice.executeShellCommand(
+ String.format(SIMPLEPERF_START_CMD, SIMPLEPERF_TMP_FILE_PATH));
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to start simpleperf.");
+ }
+ }
+ }.start();
+
+ if (!isSimpleperfRunning()) {
+ Log.e(LOG_TAG, "Simpleperf sampling failed to start.");
+ return false;
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Unable to start simpleperf sampling due to :" + e.getMessage());
+ return false;
+ }
+ Log.i(LOG_TAG, "Simpleperf sampling started successfully.");
+ return true;
+ }
+
+ /**
+ * Stop the simpleperf sample collection under /data/local/tmp/perf.data and copy the output to
+ * the destination file.
+ *
+ * @param destinationFile file to copy the simpleperf sample file to.
+ * @return true if the trace collection is successful otherwise false.
+ */
+ public boolean stopCollecting(String destinationFile) {
+ Log.i(LOG_TAG, "Stopping simpleperf.");
+ try {
+ if (stopSimpleperf()) {
+ if (!copyFileOutput(destinationFile)) {
+ return false;
+ }
+ } else {
+ Log.e(LOG_TAG, "Simpleperf failed to stop");
+ return false;
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Unable to stop the simpleperf samping due to " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Utility method for sending the signal to stop simpleperf.
+ *
+ * @return true if simpleperf is successfully stopped.
+ */
+ public boolean stopSimpleperf() throws IOException {
+ if (!isSimpleperfRunning()) {
+ Log.e(LOG_TAG, "Simpleperf stop called, but simpleperf is not running.");
+ return false;
+ }
+
+ String stopOutput = mUiDevice.executeShellCommand(SIMPLEPERF_STOP_CMD);
+ Log.i(LOG_TAG, String.format("Simpleperf stop command ran"));
+ int waitCount = 0;
+ while (isSimpleperfRunning()) {
+ if (waitCount < SIMPLEPERF_STOP_WAIT_COUNT) {
+ SystemClock.sleep(SIMPLEPERF_STOP_WAIT_TIME);
+ waitCount++;
+ continue;
+ }
+ return false;
+ }
+ Log.e(LOG_TAG, "Simpleperf stopped successfully.");
+ return true;
+ }
+
+ /**
+ * Check if there is a simpleperf instance running.
+ *
+ * @return true if there is a running simpleperf instance, otherwise false.
+ */
+ private boolean isSimpleperfRunning() {
+ try {
+ String simpleperfProcId = mUiDevice.executeShellCommand(SIMPLEPERF_PROC_ID_CMD);
+ Log.i(LOG_TAG, String.format("Simpleperf process id - %s", simpleperfProcId));
+ if (simpleperfProcId.isEmpty()) {
+ return false;
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Unable to check simpleperf status: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copy the temporary simpleperf output file to the given destinationFile.
+ *
+ * @param destinationFile file to copy simpleperf output into.
+ * @return true if the simpleperf file copied successfully, otherwise false.
+ */
+ private boolean copyFileOutput(String destinationFile) {
+ Path path = Paths.get(destinationFile);
+ String destDirectory = path.getParent().toString();
+ // Check if directory already exists
+ File directory = new File(destDirectory);
+ if (!directory.exists()) {
+ boolean success = directory.mkdirs();
+ if (!success) {
+ Log.e(
+ LOG_TAG,
+ String.format(
+ "Result output directory %s not created successfully.",
+ destDirectory));
+ return false;
+ }
+ }
+
+ // Copy the collected trace from /data/local/tmp to the destinationFile.
+ try {
+ String moveResult =
+ mUiDevice.executeShellCommand(
+ String.format(MOVE_CMD, SIMPLEPERF_TMP_FILE_PATH, destinationFile));
+ if (!moveResult.isEmpty()) {
+ Log.e(
+ LOG_TAG,
+ String.format(
+ "Unable to move simpleperf output file from %s to %s due to %s",
+ SIMPLEPERF_TMP_FILE_PATH, destinationFile, moveResult));
+ return false;
+ }
+ } catch (IOException e) {
+ Log.e(
+ LOG_TAG,
+ "Unable to move the simpleperf sample file to destination file."
+ + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/libraries/collectors-helper/simpleperf/test/Android.bp b/libraries/collectors-helper/simpleperf/test/Android.bp
new file mode 100644
index 000000000..860dbac95
--- /dev/null
+++ b/libraries/collectors-helper/simpleperf/test/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 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.
+
+java_library {
+ name: "simpleperf-helper-test",
+ defaults: ["tradefed_errorprone_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.runner",
+ "android-support-test",
+ "simpleperf-helper",
+ "junit",
+
+ ],
+
+ sdk_version: "current",
+}
diff --git a/libraries/collectors-helper/simpleperf/test/src/com/android/helpers/tests/SimpleperfHelperTest.java b/libraries/collectors-helper/simpleperf/test/src/com/android/helpers/tests/SimpleperfHelperTest.java
new file mode 100644
index 000000000..7c2fdd7a5
--- /dev/null
+++ b/libraries/collectors-helper/simpleperf/test/src/com/android/helpers/tests/SimpleperfHelperTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.helpers.tests;
+
+import android.support.test.uiautomator.UiDevice;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.helpers.SimpleperfHelper;
+
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Android Unit tests for {@link SimpleperfHelper}.
+ *
+ * <p>atest CollectorsHelperTest:com.android.helpers.tests.SimpleperfHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class SimpleperfHelperTest {
+
+ private static final String REMOVE_CMD = "rm %s";
+ private static final String FILE_SIZE_IN_BYTES = "wc -c %s";
+
+ private SimpleperfHelper simpleperfHelper;
+
+ @Before
+ public void setUp() {
+ simpleperfHelper = new SimpleperfHelper();
+ }
+
+ @After
+ public void teardown() throws IOException {
+ simpleperfHelper.stopCollecting("data/local/tmp/perf.data");
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ uiDevice.executeShellCommand(String.format(REMOVE_CMD, "/data/local/tmp/perf.data"));
+ }
+
+ /** Test simpleperf collection starts collecting properly. */
+ @Test
+ public void testSimpleperfStartSuccess() throws Exception {
+ assertTrue(simpleperfHelper.startCollecting());
+ }
+
+ /** Test if the path name is prefixed with /. */
+ @Test
+ public void testSimpleperfValidOutputPath() throws Exception {
+ assertTrue(simpleperfHelper.startCollecting());
+ assertTrue(simpleperfHelper.stopCollecting("data/local/tmp/perf.data"));
+ }
+
+ /** Test the invalid output path. */
+ @Test
+ public void testSimpleperfInvalidOutputPath() throws Exception {
+ assertTrue(simpleperfHelper.startCollecting());
+ // Don't have permission to create new folder under /data
+ assertFalse(simpleperfHelper.stopCollecting("/data/dummy/xyz/perf.data"));
+ }
+
+ /** Test simpleperf collection returns true and output file size greater than zero */
+ @Test
+ public void testSimpleperfSuccess() throws Exception {
+ assertTrue(simpleperfHelper.startCollecting());
+ Thread.sleep(1000);
+ assertTrue(simpleperfHelper.stopCollecting("/data/local/tmp/perf.data"));
+ Thread.sleep(1000);
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ String[] fileStats =
+ uiDevice.executeShellCommand(
+ String.format(FILE_SIZE_IN_BYTES, "/data/local/tmp/perf.data"))
+ .split(" ");
+ int fileSize = Integer.parseInt(fileStats[0].trim());
+ assertTrue(fileSize > 0);
+ }
+}
diff --git a/libraries/device-collectors/src/main/Android.bp b/libraries/device-collectors/src/main/Android.bp
index 9ae62aaac..5da043a01 100644
--- a/libraries/device-collectors/src/main/Android.bp
+++ b/libraries/device-collectors/src/main/Android.bp
@@ -25,6 +25,7 @@ java_library {
"memory-helper",
"perfetto-helper",
"power-helper",
+ "simpleperf-helper",
"ub-uiautomator",
"system-metric-helper",
],
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/SimpleperfListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/SimpleperfListener.java
new file mode 100644
index 000000000..61fc87a5b
--- /dev/null
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/SimpleperfListener.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 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 android.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+import android.os.Bundle;
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+import com.android.helpers.SimpleperfHelper;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+
+/**
+ * A {@link SimpleperfListener} that captures simpleperf samples for a test run or per test method
+ * run and saves the results under
+ * <root_folder>/<test_display_name>/SimpleperfListener/<test_display_name>-<invocation_count>.perf
+ */
+@OptionClass(alias = "simpleperf-collector")
+public class SimpleperfListener extends BaseMetricListener {
+
+ // Default output folder to store the simpleperf sample files.
+ private static final String DEFAULT_OUTPUT_ROOT = "/sdcard/test_results";
+ // Destination directory to save the trace results.
+ private static final String TEST_OUTPUT_ROOT = "test_output_root";
+ // Simpleperf file path key.
+ private static final String SIMPLEPERF_FILE_PATH = "simpleperf_file_path";
+ // Argument determining whether we collect for the entire run, or per test.
+ public static final String COLLECT_PER_RUN = "per_run";
+ public static final String SIMPLEPERF_PREFIX = "simpleperf_";
+ // Skip failure metrics collection if set to true.
+ public static final String SKIP_TEST_FAILURE_METRICS = "skip_test_failure_metrics";
+
+ // Simpleperf samples collected during the test will be saved under this root folder.
+ private String mTestOutputRoot;
+ // Store the method name and invocation count to create a unique filename for each trace.
+ private Map<String, Integer> mTestIdInvocationCount = new HashMap<>();
+ private boolean mSimpleperfStartSuccess = false;
+ private boolean mIsCollectPerRun;
+ private boolean mIsTestFailed = false;
+ private boolean mSkipTestFailureMetrics;
+
+ private SimpleperfHelper mSimpleperfHelper = new SimpleperfHelper();
+
+ public SimpleperfListener() {
+ super();
+ }
+
+ /**
+ * Constructor to simulate receiving the instrumentation arguments. Shoud not be used except for
+ * testing.
+ */
+ @VisibleForTesting
+ SimpleperfListener(Bundle args, SimpleperfHelper helper, Map invocationMap) {
+ super(args);
+ mSimpleperfHelper = helper;
+ mTestIdInvocationCount = invocationMap;
+ }
+
+ @Override
+ public void onTestRunStart(DataRecord runData, Description description) {
+ Bundle args = getArgsBundle();
+
+ // Whether to collect for the entire run, or per test.
+ mIsCollectPerRun = Boolean.parseBoolean(args.getString(COLLECT_PER_RUN));
+
+ // Destination folder in the device to save all simpleperf sample files.
+ // Defaulted to /sdcard/test_results if test_output_root is not passed.
+ mTestOutputRoot = args.getString(TEST_OUTPUT_ROOT, DEFAULT_OUTPUT_ROOT);
+
+ // By default this flag is set to false to collect metrics on test failure.
+ mSkipTestFailureMetrics = "true".equals(args.getString(SKIP_TEST_FAILURE_METRICS));
+
+ if (!mIsCollectPerRun) {
+ return;
+ }
+
+ Log.i(getTag(), "Starting simpleperf before test run started.");
+ startSimpleperf();
+ }
+
+ @Override
+ public void onTestStart(DataRecord testData, Description description) {
+ mIsTestFailed = false;
+ if (mIsCollectPerRun) {
+ return;
+ }
+
+ mTestIdInvocationCount.compute(
+ getTestFileName(description), (key, value) -> (value == null) ? 1 : value + 1);
+ Log.i(getTag(), "Starting simpleperf before test started.");
+ startSimpleperf();
+ }
+
+ @Override
+ public void onTestFail(DataRecord testData, Description description, Failure failure) {
+ mIsTestFailed = true;
+ }
+
+ @Override
+ public void onTestEnd(DataRecord testData, Description description) {
+ if (mIsCollectPerRun) {
+ return;
+ }
+
+ if (!mSimpleperfStartSuccess) {
+ Log.i(
+ getTag(),
+ "Skipping simpleperf stop attempt onTestEnd because simpleperf did not start"
+ + "successfully");
+ return;
+ }
+
+ if (mSkipTestFailureMetrics && mIsTestFailed) {
+ Log.i(getTag(), "Skipping metric collection due to test failure");
+ // Stop the existing simpleperf session.
+ try {
+ if (!mSimpleperfHelper.stopSimpleperf()) {
+ Log.e(getTag(), "Failed to stop the simpleperf process.");
+ }
+ } catch (IOException e) {
+ Log.e(getTag(), "Failed to stop simpleperf", e);
+ }
+ } else {
+ Log.i(getTag(), "Stopping simpleperf after test ended.");
+ // Construct test output directory in the below format
+ // <root_folder>/<test_name>/SimpleperfListener/<test_name>-<count>.data
+ Path path =
+ Paths.get(
+ mTestOutputRoot,
+ getTestFileName(description),
+ this.getClass().getSimpleName(),
+ String.format(
+ "%s%s-%d.data",
+ SIMPLEPERF_PREFIX,
+ getTestFileName(description),
+ mTestIdInvocationCount.get(getTestFileName(description))));
+ stopSimpleperf(path, testData);
+ }
+ }
+
+ @Override
+ public void onTestRunEnd(DataRecord runData, Result result) {
+ if (!mIsCollectPerRun) {
+ return;
+ }
+
+ if (!mSimpleperfStartSuccess) {
+ Log.i(getTag(), "Skipping simpleperf stop attempt as simpleperf failed to start.");
+ return;
+ }
+
+ Log.i(getTag(), "Stopping simpleperf after test run ended");
+ Path path =
+ Paths.get(
+ mTestOutputRoot,
+ this.getClass().getSimpleName(),
+ String.format(
+ "%s%d.data", SIMPLEPERF_PREFIX, UUID.randomUUID().hashCode()));
+ stopSimpleperf(path, runData);
+ }
+
+ /** Start simpleperf sampling. */
+ public void startSimpleperf() {
+ mSimpleperfStartSuccess = mSimpleperfHelper.startCollecting();
+ if (!mSimpleperfStartSuccess) {
+ Log.e(getTag(), "Simpleperf did not start successfully.");
+ }
+ }
+
+ /** Stop simpleperf sampling and dump the collected file into the given path. */
+ private void stopSimpleperf(Path path, DataRecord record) {
+ if (!mSimpleperfHelper.stopCollecting(path.toString())) {
+ Log.e(getTag(), "Failed to collect the simpleperf output.");
+ } else {
+ record.addStringMetric(SIMPLEPERF_FILE_PATH, path.toString());
+ }
+ }
+
+ /**
+ * Returns the packagename.classname_methodname which has no special characters and is used to
+ * create file names.
+ */
+ public static String getTestFileName(Description description) {
+ return String.format("%s_%s", description.getClassName(), description.getMethodName());
+ }
+}
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/SimpleperfListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/SimpleperfListenerTest.java
new file mode 100644
index 000000000..7473f71a6
--- /dev/null
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/SimpleperfListenerTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2020 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 android.device.collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.helpers.SimpleperfHelper;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/**
+ * Android Unit tests for {@link SimpleperfListener}.
+ *
+ * <p>To run: atest CollectorDeviceLibTest:android.device.collectors.SimpleperfListenerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class SimpleperfListenerTest {
+
+ // A {@code Description} to pass when faking a test run start call.
+ private static final Description FAKE_DESCRIPTION = Description.createSuiteDescription("run");
+
+ private static final Description FAKE_TEST_DESCRIPTION =
+ Description.createTestDescription("class", "method");
+
+ private Description mRunDesc;
+ private Description mTest1Desc;
+ private Description mTest2Desc;
+ private SimpleperfListener mListener;
+ @Mock private Instrumentation mInstrumentation;
+ private Map<String, Integer> mInvocationCount;
+ private DataRecord mDataRecord;
+
+ @Spy private SimpleperfHelper mSimpleperfHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRunDesc = Description.createSuiteDescription("run");
+ mTest1Desc = Description.createTestDescription("run", "test1");
+ mTest2Desc = Description.createTestDescription("run", "test2");
+ }
+
+ private SimpleperfListener initListener(Bundle b) {
+ mInvocationCount = new HashMap<>();
+
+ SimpleperfListener listener =
+ spy(new SimpleperfListener(b, mSimpleperfHelper, mInvocationCount));
+
+ mDataRecord = listener.createDataRecord();
+ listener.setInstrumentation(mInstrumentation);
+ return listener;
+ }
+
+ /*
+ * Verify simpleperf start and stop collection methods called exactly once for single test.
+ */
+ @Test
+ public void testSimpleperfPerTestSuccessFlow() throws Exception {
+ Bundle b = new Bundle();
+ mListener = initListener(b);
+ doReturn(true).when(mSimpleperfHelper).startCollecting();
+ doReturn(true).when(mSimpleperfHelper).stopCollecting(anyString());
+ // Test run start behavior
+ mListener.testRunStarted(mRunDesc);
+
+ // Test test start behavior
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).stopCollecting(anyString());
+ }
+
+ /*
+ * Verify stop collecting called exactly once when the test failed and the
+ * skip test failure mmetrics is enabled.
+ */
+ @Test
+ public void testSimpleperfPerTestFailureFlowDefault() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(SimpleperfListener.SKIP_TEST_FAILURE_METRICS, "false");
+ mListener = initListener(b);
+
+ doReturn(true).when(mSimpleperfHelper).startCollecting();
+ doReturn(true).when(mSimpleperfHelper).stopCollecting(anyString());
+ // Test run start behavior
+ mListener.testRunStarted(mRunDesc);
+
+ // Test test start behavior
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+
+ // Test fail behaviour
+ Failure failureDesc = new Failure(FAKE_TEST_DESCRIPTION, new Exception());
+ mListener.onTestFail(mDataRecord, mTest1Desc, failureDesc);
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).stopCollecting(anyString());
+ }
+
+ /*
+ * Verify stop simpleperf called exactly once when the test failed and the
+ * skip test failure metrics is enabled.
+ */
+ @Test
+ public void testSimpleperfPerTestFailureFlowWithSkipMmetrics() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(SimpleperfListener.SKIP_TEST_FAILURE_METRICS, "true");
+ mListener = initListener(b);
+
+ doReturn(true).when(mSimpleperfHelper).startCollecting();
+ doReturn(true).when(mSimpleperfHelper).stopSimpleperf();
+ // Test run start behavior
+ mListener.testRunStarted(mRunDesc);
+
+ // Test test start behavior
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+
+ // Test fail behaviour
+ Failure failureDesc = new Failure(FAKE_TEST_DESCRIPTION, new Exception());
+ mListener.onTestFail(mDataRecord, mTest1Desc, failureDesc);
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).stopSimpleperf();
+ }
+
+ /*
+ * Verify simpleperf start and stop collection methods called exactly once for test run.
+ * and not during each test method.
+ */
+ @Test
+ public void testSimpleperfPerRunSuccessFlow() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(SimpleperfListener.COLLECT_PER_RUN, "true");
+ mListener = initListener(b);
+ doReturn(true).when(mSimpleperfHelper).startCollecting();
+ doReturn(true).when(mSimpleperfHelper).stopCollecting(anyString());
+
+ // Test run start behavior
+ mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(0)).stopCollecting(anyString());
+ mListener.onTestRunEnd(mListener.createDataRecord(), new Result());
+ verify(mSimpleperfHelper, times(1)).stopCollecting(anyString());
+ }
+
+ /*
+ * Verify stop is not called if Simpleperf start did not succeed.
+ */
+ @Test
+ public void testSimpleperfPerRunFailureFlow() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(SimpleperfListener.COLLECT_PER_RUN, "true");
+ mListener = initListener(b);
+ doReturn(false).when(mSimpleperfHelper).startCollecting();
+
+ // Test run start behavior
+ mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.onTestRunEnd(mListener.createDataRecord(), new Result());
+ verify(mSimpleperfHelper, times(0)).stopCollecting(anyString());
+ }
+
+ /*
+ * Verify simpleperf stop is not invoked if start did not succeed.
+ */
+ @Test
+ public void testSimpleperfStartFailureFlow() throws Exception {
+ Bundle b = new Bundle();
+ mListener = initListener(b);
+ doReturn(false).when(mSimpleperfHelper).startCollecting();
+
+ // Test run start behavior
+ mListener.testRunStarted(mRunDesc);
+
+ // Test test start behavior
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(0)).stopCollecting(anyString());
+ }
+
+ /*
+ * Verify test method invocation count is updated successfully based on the number of times the
+ * test method is invoked.
+ */
+ @Test
+ public void testSimpleperfInvocationCount() throws Exception {
+ Bundle b = new Bundle();
+ mListener = initListener(b);
+ doReturn(true).when(mSimpleperfHelper).startCollecting();
+ doReturn(true).when(mSimpleperfHelper).stopCollecting(anyString());
+
+ // Test run start behavior
+ mListener.testRunStarted(mRunDesc);
+
+ // Test1 invocation 1 start behavior
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).startCollecting();
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(1)).stopCollecting(anyString());
+
+ // Test1 invocation 2 start behaviour
+ mListener.testStarted(mTest1Desc);
+ verify(mSimpleperfHelper, times(2)).startCollecting();
+ mListener.onTestEnd(mDataRecord, mTest1Desc);
+ verify(mSimpleperfHelper, times(2)).stopCollecting(anyString());
+
+ // Test2 invocation 1 start behaviour
+ mListener.testStarted(mTest2Desc);
+ verify(mSimpleperfHelper, times(3)).startCollecting();
+ mDataRecord = mListener.createDataRecord();
+ mListener.onTestEnd(mDataRecord, mTest2Desc);
+ verify(mSimpleperfHelper, times(3)).stopCollecting(anyString());
+
+ // Check if the test count is incremented properly.
+ assertEquals(2, (int) mInvocationCount.get(mListener.getTestFileName(mTest1Desc)));
+ assertEquals(1, (int) mInvocationCount.get(mListener.getTestFileName(mTest2Desc)));
+ }
+}
diff --git a/libraries/health/runners/longevity/host/src/android/host/test/longevity/listener/TimeoutTerminator.java b/libraries/health/runners/longevity/host/src/android/host/test/longevity/listener/TimeoutTerminator.java
index 5204e44ef..65e6a0d20 100644
--- a/libraries/health/runners/longevity/host/src/android/host/test/longevity/listener/TimeoutTerminator.java
+++ b/libraries/health/runners/longevity/host/src/android/host/test/longevity/listener/TimeoutTerminator.java
@@ -44,7 +44,7 @@ public class TimeoutTerminator extends RunTerminator {
* <p>Note: this initializes the countdown timer if unset.
*/
@Override
- public void testRunStarted(Description description) {
+ public void testStarted(Description description) {
if (mStartTimestamp == UNSET_TIMESTAMP) {
mStartTimestamp = getCurrentTimestamp();
}
diff --git a/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/LongevitySuiteTest.java b/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/LongevitySuiteTest.java
index 261a87d80..d21a61f29 100644
--- a/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/LongevitySuiteTest.java
+++ b/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/LongevitySuiteTest.java
@@ -18,6 +18,8 @@ package android.host.test.longevity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import android.host.test.longevity.listener.TimeoutTerminator;
+
import java.util.HashMap;
import java.util.Map;
@@ -81,7 +83,7 @@ public class LongevitySuiteTest {
FailingTestSuite.class, new AllDefaultPossibilitiesBuilder(true), args);
try {
suite.run(new RunNotifier());
- fail("This run should be invalidated by test failures.");
+ fail("This run should be invalidated by test failure.");
} catch (StoppedByUserException e) {
// Expect this failure for an invalid, erroring test run.
}
@@ -91,9 +93,7 @@ public class LongevitySuiteTest {
@SuiteClasses({
FailingTestSuite.FailingTest.class,
})
- /**
- * Sample device-side test cases.
- */
+ /** Sample device-side test case that fails. */
public static class FailingTestSuite {
public static class FailingTest {
@Test
@@ -103,6 +103,38 @@ public class LongevitySuiteTest {
}
}
+ /** Tests that test runs are timing out if the tests run over the allotted suite time. */
+ @Test
+ public void testTimeoutTestRuns() throws InitializationError {
+ Map<String, String> args = new HashMap();
+ args.put(LongevitySuite.INVALIDATE_OPTION, "true");
+ args.put(TimeoutTerminator.OPTION, "25");
+ args.put(ITERATIONS_OPTION_NAME, String.valueOf(10));
+ LongevitySuite suite =
+ new LongevitySuite(
+ TimeoutTestSuite.class, new AllDefaultPossibilitiesBuilder(true), args);
+ try {
+ suite.run(new RunNotifier());
+ fail("This run should be ended by a timeout failure.");
+ } catch (StoppedByUserException e) {
+ // Expect this failure for an invalid, erroring test run.
+ }
+ }
+
+ @RunWith(LongevitySuite.class)
+ @SuiteClasses({
+ TimeoutTestSuite.TimedTest.class,
+ })
+ /** Sample device-side test case that takes time. */
+ public static class TimeoutTestSuite {
+ public static class TimedTest {
+ @Test
+ public void testSleep() throws InterruptedException {
+ Thread.sleep(10);
+ }
+ }
+ }
+
/**
* Tests that the {@link LongevitySuite} properly accounts for the number of tests in children.
*/
diff --git a/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/listener/TimeoutTerminatorTest.java b/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/listener/TimeoutTerminatorTest.java
index 1862e11b8..c439721f5 100644
--- a/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/listener/TimeoutTerminatorTest.java
+++ b/libraries/health/runners/longevity/host/tests/src/android/host/test/longevity/listener/TimeoutTerminatorTest.java
@@ -50,7 +50,7 @@ public class TimeoutTerminatorTest {
*/
@Test
public void testTimeoutTerminator_pass() throws Exception {
- mListener.testRunStarted(Description.EMPTY);
+ mListener.testStarted(Description.EMPTY);
Thread.sleep(10L);
mListener.testFinished(Description.EMPTY);
verify(mNotifier, never()).pleaseStop();
@@ -61,7 +61,7 @@ public class TimeoutTerminatorTest {
*/
@Test
public void testTimeoutTerminator_timeout() throws Exception {
- mListener.testRunStarted(Description.EMPTY);
+ mListener.testStarted(Description.EMPTY);
Thread.sleep(60L);
mListener.testFinished(Description.EMPTY);
verify(mNotifier).pleaseStop();
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevityClassRunner.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevityClassRunner.java
index e565427e1..f1cc67d2b 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevityClassRunner.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevityClassRunner.java
@@ -38,6 +38,7 @@ import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
+import org.junit.runner.notification.StoppedByUserException;
/**
* A {@link BlockJUnit4ClassRunner} that runs the test class's {@link BeforeClass} methods as {@link
@@ -229,12 +230,16 @@ public class LongevityClassRunner extends BlockJUnit4ClassRunner {
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<>();
+ boolean stoppedByUser = false;
try {
mStatement.evaluate();
} catch (Throwable e) {
+ if (e instanceof StoppedByUserException) {
+ stoppedByUser = true;
+ }
errors.add(e);
} finally {
- if (LongevityClassRunner.this.hasTestFailed()) {
+ if (!stoppedByUser && LongevityClassRunner.this.hasTestFailed()) {
errors.addAll(invokeAndCollectErrors(mAfterClassMethods, mTarget));
}
}
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevitySuite.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevitySuite.java
index ced687480..2e0df1c53 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevitySuite.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/LongevitySuite.java
@@ -201,9 +201,7 @@ public class LongevitySuite extends android.host.test.longevity.LongevitySuite {
super.runChild(suiteRunner, notifier);
}
- /**
- * Returns the platform-specific {@link TimeoutTerminator} for Android devices.
- */
+ /** Returns the platform-specific {@link ErrorTerminator} for an Android device. */
@Override
public android.host.test.longevity.listener.ErrorTerminator getErrorTerminator(
final RunNotifier notifier) {
@@ -211,7 +209,7 @@ public class LongevitySuite extends android.host.test.longevity.LongevitySuite {
}
/**
- * Returns the platform-specific {@link TimeoutTerminator} for Android devices.
+ * Returns the platform-specific {@link TimeoutTerminator} for an Android device.
*
* <p>This method will always return the same {@link TimeoutTerminator} instance.
*/
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/listener/TimeoutTerminatorTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/listener/TimeoutTerminatorTest.java
index 76749ffb3..a6dfb79e9 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/listener/TimeoutTerminatorTest.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/listener/TimeoutTerminatorTest.java
@@ -53,7 +53,7 @@ public class TimeoutTerminatorTest {
*/
@Test
public void testTimeoutTerminator_pass() throws Exception {
- mListener.testRunStarted(Description.EMPTY);
+ mListener.testStarted(Description.EMPTY);
SystemClock.sleep(10L);
verify(mNotifier, never()).pleaseStop();
}
@@ -63,7 +63,7 @@ public class TimeoutTerminatorTest {
*/
@Test
public void testTimeoutTerminator_timeout() throws Exception {
- mListener.testRunStarted(Description.EMPTY);
+ mListener.testStarted(Description.EMPTY);
SystemClock.sleep(60L);
mListener.testFinished(Description.EMPTY);
verify(mNotifier).pleaseStop();