From 1779a38290933a2b72df87795b5c8f1758da2ece Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 25 Feb 2020 14:41:01 -0800 Subject: [PATCH] Using StrictMode to detect activity leaks Change-Id: I615f641897d57be0cd31be944796c6931ef9ab00 --- .../quickstep/NavigationModeSwitchRule.java | 3 +- .../launcher3/ui/AbstractLauncherUiTest.java | 40 +++++++++++++++++++ .../launcher3/ui/PortraitLandscapeRunner.java | 2 + .../launcher3/ui/TaplTestsLauncher3.java | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 1229a637b4..cd94704228 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -35,6 +35,7 @@ import androidx.test.uiautomator.UiDevice; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.tapl.TestHelpers; +import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; import com.android.systemui.shared.system.QuickStepContract; @@ -199,7 +200,7 @@ public class NavigationModeSwitchRule implements TestRule { + launcher.getNavigationModeMismatchError(), () -> launcher.getNavigationModeMismatchError() == null, 60000 /* b/148422894 */, launcher); - + AbstractLauncherUiTest.checkDetectedLeaks(); return true; } diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 5e98184cc5..afdf378400 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -37,6 +37,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Process; import android.os.RemoteException; +import android.os.StrictMode; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; @@ -95,6 +96,9 @@ public abstract class AbstractLauncherUiTest { public static final long DEFAULT_UI_TIMEOUT = 10000; private static final String TAG = "AbstractLauncherUiTest"; + private static String sDetectedActivityLeak; + private static boolean sActivityLeakReported; + protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation(); @@ -102,6 +106,41 @@ public abstract class AbstractLauncherUiTest { protected String mTargetPackage; private int mLauncherPid; + static { + if (TestHelpers.isInLauncherProcess()) { + StrictMode.VmPolicy.Builder builder = + new StrictMode.VmPolicy.Builder() + .detectActivityLeaks() + .penaltyLog() + .penaltyListener(Runnable::run, violation -> { + // Runs in the main thread. We can't dumpheap in the main thread, + // so let's just mark the fact that the leak has happened. + if (sDetectedActivityLeak == null) { + sDetectedActivityLeak = violation.toString(); + } + }); + StrictMode.setVmPolicy(builder.build()); + } + } + + public static void checkDetectedLeaks() { + if (sDetectedActivityLeak != null && !sActivityLeakReported) { + sActivityLeakReported = true; + + final UiDevice device = UiDevice.getInstance(getInstrumentation()); + try { + device.executeShellCommand( + "am dumpheap " + + device.getLauncherPackageName() + + " " + + getInstrumentation().getTargetContext().getFilesDir().getPath() + + "/ActivityLeakHeapDump.hprof"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + protected AbstractLauncherUiTest() { mLauncher.enableCheckEventsForSuccessfulGestures(); try { @@ -190,6 +229,7 @@ public abstract class AbstractLauncherUiTest { if (mLauncherPid != 0) { assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid()); } + checkDetectedLeaks(); } protected void clearLauncherData() throws IOException, InterruptedException { diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java index 1a68122217..38f50c1ef2 100644 --- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java +++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java @@ -56,6 +56,7 @@ class PortraitLandscapeRunner implements TestRule { private void evaluateInPortrait() throws Throwable { mTest.mDevice.setOrientationNatural(); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); + AbstractLauncherUiTest.checkDetectedLeaks(); base.evaluate(); mTest.getDevice().pressHome(); } @@ -63,6 +64,7 @@ class PortraitLandscapeRunner implements TestRule { private void evaluateInLandscape() throws Throwable { mTest.mDevice.setOrientationLeft(); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90); + AbstractLauncherUiTest.checkDetectedLeaks(); base.evaluate(); mTest.getDevice().pressHome(); } diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 7475efeff0..dec222f414 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -65,6 +65,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { test.waitForResumed("Launcher internal state is still Background"); // Check that we switched to home. test.mLauncher.getWorkspace(); + AbstractLauncherUiTest.checkDetectedLeaks(); } // Please don't add negative test cases for methods that fail only after a long wait.