From 25fbd5b0bbb4e1fe0fe33f525235e6bbb6081b30 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Apr 2022 17:31:28 -0700 Subject: [PATCH] Sampling too long Launcher tests A test that takes > 3 min will generate an artifact file containing stacks of all threads of the test process taken every 3 sec. This artifact will be also generated if the test process is killed, for example, by timeout. This artifact should help EngProd's effort to speed up presubmits. Bug: 225186335 Test: local runs Change-Id: I721779bfbe5bc6289315998ed2660f5f46165611 --- .../quickstep/FallbackRecentsTest.java | 4 +- tests/Android.bp | 1 + .../launcher3/ui/AbstractLauncherUiTest.java | 4 +- .../launcher3/util/rule/SamplerRule.java | 129 ++++++++++++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/src/com/android/launcher3/util/rule/SamplerRule.java diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index ca6712f768..f7600ff9bb 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -57,6 +57,7 @@ import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; +import com.android.launcher3.util.rule.SamplerRule; import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.quickstep.views.RecentsView; @@ -111,7 +112,8 @@ public class FallbackRecentsTest { } mOrderSensitiveRules = RuleChain - .outerRule(new NavigationModeSwitchRule(mLauncher)) + .outerRule(new SamplerRule()) + .around(new NavigationModeSwitchRule(mLauncher)) .around(new FailureWatcher(mDevice, mLauncher)); mOtherLauncherActivity = context.getPackageManager().queryIntentActivities( diff --git a/tests/Android.bp b/tests/Android.bp index 7542d04f7b..54cded0fd3 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -37,6 +37,7 @@ filegroup { "src/com/android/launcher3/util/WidgetUtils.java", "src/com/android/launcher3/util/rule/FailureWatcher.java", "src/com/android/launcher3/util/rule/LauncherActivityRule.java", + "src/com/android/launcher3/util/rule/SamplerRule.java", "src/com/android/launcher3/util/rule/ScreenRecordRule.java", "src/com/android/launcher3/util/rule/ShellCommandRule.java", "src/com/android/launcher3/util/rule/SimpleActivityRule.java", diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 136f115650..7080c85690 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -68,6 +68,7 @@ import com.android.launcher3.util.Wait; import com.android.launcher3.util.WidgetUtils; import com.android.launcher3.util.rule.FailureWatcher; import com.android.launcher3.util.rule.LauncherActivityRule; +import com.android.launcher3.util.rule.SamplerRule; import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.util.rule.TestStabilityRule; @@ -227,7 +228,8 @@ public abstract class AbstractLauncherUiTest { @Rule public TestRule mOrderSensitiveRules = RuleChain - .outerRule(new TestStabilityRule()) + .outerRule(new SamplerRule()) + .around(new TestStabilityRule()) .around(mActivityMonitor) .around(getRulesInsideActivityMonitor()); diff --git a/tests/src/com/android/launcher3/util/rule/SamplerRule.java b/tests/src/com/android/launcher3/util/rule/SamplerRule.java new file mode 100644 index 0000000000..6125f2a8d2 --- /dev/null +++ b/tests/src/com/android/launcher3/util/rule/SamplerRule.java @@ -0,0 +1,129 @@ +/* + * 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.launcher3.util.rule; + +import android.os.SystemClock; + +import androidx.test.InstrumentationRegistry; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * A rule that generates a file that helps diagnosing cases when the test process was terminated + * because the test execution took too long, and tests that ran for too long even without being + * terminated. If the process was terminated or the test was long, the test leaves an artifact with + * stack traces of all threads, every SAMPLE_INTERVAL_MS. This will help understanding where we + * stuck. + */ +public class SamplerRule implements TestRule { + private static final int TOO_LONG_TEST_MS = 180000; + private static final int SAMPLE_INTERVAL_MS = 3000; + + public static Thread startThread(Description description) { + Thread thread = + new Thread() { + @Override + public void run() { + // Write all-threads stack stace every SAMPLE_INTERVAL_MS while the test + // is running. + // After the test finishes, delete that file. If the test process is + // terminated due to timeout, the trace file won't be deleted. + final File file = getFile(); + + final long startTime = SystemClock.elapsedRealtime(); + try (OutputStreamWriter outputStreamWriter = + new OutputStreamWriter( + new BufferedOutputStream( + new FileOutputStream(file)))) { + writeSamples(outputStreamWriter); + } catch (IOException | InterruptedException e) { + // Simply suppressing the exceptions, nothing to do here. + } finally { + // If the process is not killed, then there was no test timeout, and + // we are not interested in the trace file, unless the test ran too + // long. + if (SystemClock.elapsedRealtime() - startTime < TOO_LONG_TEST_MS) { + file.delete(); + } + } + } + + private File getFile() { + final String strDate = new SimpleDateFormat("HH:mm:ss").format(new Date()); + + final String descStr = description.getTestClass().getSimpleName() + "." + + description.getMethodName(); + return artifactFile( + "ThreadStackSamples-" + strDate + "-" + descStr + ".txt"); + } + + private void writeSamples(OutputStreamWriter writer) + throws IOException, InterruptedException { + int count = 0; + while (true) { + writer.write( + "#" + + (count++) + + " =============================================\r\n"); + for (StackTraceElement[] stack : getAllStackTraces().values()) { + writer.write("---------------------\r\n"); + for (StackTraceElement frame : stack) { + writer.write(frame.toString() + "\r\n"); + } + } + writer.flush(); + + sleep(SAMPLE_INTERVAL_MS); + } + } + }; + + thread.start(); + return thread; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + final Thread traceThread = startThread(description); + try { + base.evaluate(); + } finally { + traceThread.interrupt(); + traceThread.join(); + } + } + }; + } + + private static File artifactFile(String fileName) { + return new File( + InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir(), + fileName); + } +}