Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d73d39b4 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Makeshift analog of Strictmode leak detector" into ub-launcher3-rvc-dev

parents 8d5a0000 b3e8ae82
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -203,7 +203,7 @@ public class NavigationModeSwitchRule implements TestRule {
                        + launcher.getNavigationModeMismatchError(),
                () -> launcher.getNavigationModeMismatchError() == null,
                60000 /* b/148422894 */, launcher);
        AbstractLauncherUiTest.checkDetectedLeaks();
        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
        return true;
    }

+39 −17
Original line number Diff line number Diff line
@@ -98,9 +98,10 @@ public abstract class AbstractLauncherUiTest {
    public static final long DEFAULT_UI_TIMEOUT = 10000;
    private static final String TAG = "AbstractLauncherUiTest";

    private static String sDetectedActivityLeak;
    private static String sStrictmodeDetectedActivityLeak;
    private static boolean sActivityLeakReported;
    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
    private static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();

    protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
    protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -113,30 +114,51 @@ public abstract class AbstractLauncherUiTest {
        if (TestHelpers.isInLauncherProcess()) {
            StrictMode.VmPolicy.Builder builder =
                    new StrictMode.VmPolicy.Builder()
                            .detectActivityLeaks()
// b/154772063
//                            .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();
                                    try {
                                        Debug.dumpHprofData(
                                                getInstrumentation().getTargetContext()
                                                        .getFilesDir().getPath()
                                                        + "/ActivityLeakHeapDump.hprof");
                                    } catch (Throwable e) {
                                        Log.e(TAG, "dumpHprofData failed", e);
                                    }
                                if (sStrictmodeDetectedActivityLeak == null) {
                                    sStrictmodeDetectedActivityLeak = violation.toString() + ", "
                                            + dumpHprofData() + ".";
                                }
                            });
            StrictMode.setVmPolicy(builder.build());
        }
    }

    public static void checkDetectedLeaks() {
        if (sDetectedActivityLeak != null && !sActivityLeakReported) {
    public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
        if (sActivityLeakReported) return;

        if (sStrictmodeDetectedActivityLeak != null) {
            // Report from the test thread strictmode violations detected in the main thread.
            sActivityLeakReported = true;
            Assert.fail(sStrictmodeDetectedActivityLeak);
        }

        // Check whether activity leak detector has found leaked activities.
        Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage,
                () -> {
                    launcher.getTotalPssKb();  // Triggers GC
                    return MAIN_EXECUTOR.submit(
                            () -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get();
                }, DEFAULT_UI_TIMEOUT, launcher);
    }

    private static String getActivityLeakErrorMessage() {
        sActivityLeakReported = true;
        return "Activity leak detector has found leaked activities, " + dumpHprofData() + ".";
    }

    private static String dumpHprofData() {
        try {
            final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
                    + "/ActivityLeakHeapDump.hprof";
            Debug.dumpHprofData(fileName);
            return "memory dump filename: " + fileName;
        } catch (Throwable e) {
            Log.e(TAG, "dumpHprofData failed", e);
            return "failed to save memory dump";
        }
    }

@@ -263,7 +285,7 @@ public abstract class AbstractLauncherUiTest {
        if (mLauncherPid != 0) {
            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
        }
        checkDetectedLeaks();
        checkDetectedLeaks(mLauncher);
    }

    protected void clearLauncherData() throws IOException, InterruptedException {
+83 −0
Original line number Diff line number Diff line
/*
 * 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.launcher3.ui;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import androidx.test.InstrumentationRegistry;

import com.android.launcher3.tapl.TestHelpers;

import java.util.WeakHashMap;

class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
    private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();

    ActivityLeakTracker() {
        if (!TestHelpers.isInLauncherProcess()) return;
        final Application app =
                (Application) InstrumentationRegistry.getTargetContext().getApplicationContext();
        app.registerActivityLifecycleCallbacks(this);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {
        mActivities.put(activity, true);
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    public boolean noLeakedActivities() {
        int liveActivities = 0;
        int destroyedActivities = 0;

        for (Activity activity : mActivities.keySet()) {
            if (activity.isDestroyed()) {
                ++destroyedActivities;
            } else {
                ++liveActivities;
            }
        }

        // It's OK to have 1 leaked activity if no active activities exist.
        return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ class PortraitLandscapeRunner implements TestRule {
            private void evaluateInPortrait() throws Throwable {
                mTest.mDevice.setOrientationNatural();
                mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
                AbstractLauncherUiTest.checkDetectedLeaks();
                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
                base.evaluate();
                mTest.getDevice().pressHome();
            }
@@ -64,7 +64,7 @@ class PortraitLandscapeRunner implements TestRule {
            private void evaluateInLandscape() throws Throwable {
                mTest.mDevice.setOrientationLeft();
                mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
                AbstractLauncherUiTest.checkDetectedLeaks();
                AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
                base.evaluate();
                mTest.getDevice().pressHome();
            }
+1 −1
Original line number Diff line number Diff line
@@ -64,7 +64,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();
        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
    }

    // Please don't add negative test cases for methods that fail only after a long wait.