Moving activity tracker to Launcher process
This will improve diagnostics for OOP tests, like we now have a list of leaked activity classes. Also some cleanups. Bug: 187761685 Test: local runs Change-Id: I8b5711ac727874fd826cfef9c742ea97048763e0
This commit is contained in:
parent
2d9741b832
commit
5ade8e890d
|
@ -18,6 +18,8 @@ package com.android.launcher3.testing;
|
||||||
|
|
||||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -31,7 +33,10 @@ import com.android.launcher3.LauncherSettings;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -41,9 +46,48 @@ import java.util.concurrent.TimeUnit;
|
||||||
public class DebugTestInformationHandler extends TestInformationHandler {
|
public class DebugTestInformationHandler extends TestInformationHandler {
|
||||||
private static LinkedList sLeaks;
|
private static LinkedList sLeaks;
|
||||||
private static Collection<String> sEvents;
|
private static Collection<String> sEvents;
|
||||||
|
private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
|
||||||
|
private static final Map<Activity, Boolean> sActivities =
|
||||||
|
Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
private static int sActivitiesCreatedCount = 0;
|
||||||
|
|
||||||
public DebugTestInformationHandler(Context context) {
|
public DebugTestInformationHandler(Context context) {
|
||||||
init(context);
|
init(context);
|
||||||
|
if (sActivityLifecycleCallbacks == null) {
|
||||||
|
sActivityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||||
|
sActivities.put(activity, true);
|
||||||
|
++sActivitiesCreatedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
((Application) context.getApplicationContext())
|
||||||
|
.registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runGcAndFinalizersSync() {
|
private static void runGcAndFinalizersSync() {
|
||||||
|
@ -160,6 +204,20 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
|
||||||
|
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TestProtocol.REQUEST_GET_ACTIVITIES: {
|
||||||
|
response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||||
|
sActivities.keySet().stream().map(
|
||||||
|
a -> a.getClass().getSimpleName() + " ("
|
||||||
|
+ (a.isDestroyed() ? "destroyed" : "current") + ")")
|
||||||
|
.toArray(String[]::new));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return super.call(method, arg);
|
return super.call(method, arg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,9 @@ public final class TestProtocol {
|
||||||
public static final String REQUEST_CLEAR_DATA = "clear-data";
|
public static final String REQUEST_CLEAR_DATA = "clear-data";
|
||||||
public static final String REQUEST_IS_TABLET = "is-tablet";
|
public static final String REQUEST_IS_TABLET = "is-tablet";
|
||||||
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
|
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
|
||||||
|
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
|
||||||
|
"get-activities-created-count";
|
||||||
|
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
|
||||||
|
|
||||||
public static Long sForcePauseTimeout;
|
public static Long sForcePauseTimeout;
|
||||||
public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
|
public static final String REQUEST_SET_FORCE_PAUSE_TIMEOUT = "set-force-pause-timeout";
|
||||||
|
|
|
@ -31,7 +31,6 @@ filegroup {
|
||||||
name: "launcher-oop-tests-src",
|
name: "launcher-oop-tests-src",
|
||||||
srcs: [
|
srcs: [
|
||||||
"src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
|
"src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
|
||||||
"src/com/android/launcher3/ui/ActivityLeakTracker.java",
|
|
||||||
"src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
|
"src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
|
||||||
"src/com/android/launcher3/util/Wait.java",
|
"src/com/android/launcher3/util/Wait.java",
|
||||||
"src/com/android/launcher3/util/WidgetUtils.java",
|
"src/com/android/launcher3/util/WidgetUtils.java",
|
||||||
|
|
|
@ -36,7 +36,6 @@ import android.content.pm.PackageManager;
|
||||||
import android.os.Debug;
|
import android.os.Debug;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.StrictMode;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -99,11 +98,9 @@ public abstract class AbstractLauncherUiTest {
|
||||||
public static final long DEFAULT_UI_TIMEOUT = 10000;
|
public static final long DEFAULT_UI_TIMEOUT = 10000;
|
||||||
private static final String TAG = "AbstractLauncherUiTest";
|
private static final String TAG = "AbstractLauncherUiTest";
|
||||||
|
|
||||||
private static String sStrictmodeDetectedActivityLeak;
|
|
||||||
private static boolean sDumpWasGenerated = false;
|
private static boolean sDumpWasGenerated = false;
|
||||||
private static boolean sActivityLeakReported;
|
private static boolean sActivityLeakReported = false;
|
||||||
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
|
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
|
||||||
protected static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
|
|
||||||
|
|
||||||
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
|
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
|
||||||
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
|
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
|
||||||
|
@ -112,45 +109,25 @@ public abstract class AbstractLauncherUiTest {
|
||||||
protected String mTargetPackage;
|
protected String mTargetPackage;
|
||||||
private int mLauncherPid;
|
private int mLauncherPid;
|
||||||
|
|
||||||
static {
|
|
||||||
if (TestHelpers.isInLauncherProcess()) {
|
|
||||||
StrictMode.VmPolicy.Builder builder =
|
|
||||||
new StrictMode.VmPolicy.Builder()
|
|
||||||
.penaltyLog()
|
|
||||||
.penaltyListener(Runnable::run, violation -> {
|
|
||||||
if (sStrictmodeDetectedActivityLeak == null) {
|
|
||||||
sStrictmodeDetectedActivityLeak = violation.toString() + ", "
|
|
||||||
+ dumpHprofData() + ".";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
StrictMode.setVmPolicy(builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
|
public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
|
||||||
if (sActivityLeakReported) return;
|
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.
|
// Check whether activity leak detector has found leaked activities.
|
||||||
Wait.atMost(AbstractLauncherUiTest::getActivityLeakErrorMessage,
|
Wait.atMost(() -> getActivityLeakErrorMessage(launcher),
|
||||||
() -> {
|
() -> {
|
||||||
launcher.forceGc();
|
launcher.forceGc();
|
||||||
return MAIN_EXECUTOR.submit(
|
return MAIN_EXECUTOR.submit(
|
||||||
() -> ACTIVITY_LEAK_TRACKER.noLeakedActivities()).get();
|
() -> launcher.noLeakedActivities()).get();
|
||||||
}, DEFAULT_UI_TIMEOUT, launcher);
|
}, DEFAULT_UI_TIMEOUT, launcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getActivityLeakErrorMessage() {
|
private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher) {
|
||||||
sActivityLeakReported = true;
|
sActivityLeakReported = true;
|
||||||
return "Activity leak detector has found leaked activities, " + dumpHprofData() + ".";
|
return "Activity leak detector has found leaked activities, "
|
||||||
|
+ dumpHprofData(launcher) + ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String dumpHprofData() {
|
public static String dumpHprofData(LauncherInstrumentation launcher) {
|
||||||
String result;
|
String result;
|
||||||
if (sDumpWasGenerated) {
|
if (sDumpWasGenerated) {
|
||||||
Log.d("b/195319692", "dump has already been generated by another test",
|
Log.d("b/195319692", "dump has already been generated by another test",
|
||||||
|
@ -176,8 +153,7 @@ public abstract class AbstractLauncherUiTest {
|
||||||
result = "failed to save memory dump";
|
result = "failed to save memory dump";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result + ". Full list of activities: " + launcher.getRootedActivitiesList();
|
||||||
+ ". Full list of activities: " + ACTIVITY_LEAK_TRACKER.getActivitiesList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractLauncherUiTest() {
|
protected AbstractLauncherUiTest() {
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ActivityLeakTracker implements Application.ActivityLifecycleCallbacks {
|
|
||||||
private final WeakHashMap<Activity, Boolean> mActivities = new WeakHashMap<>();
|
|
||||||
|
|
||||||
private int mActivitiesCreated;
|
|
||||||
|
|
||||||
ActivityLeakTracker() {
|
|
||||||
if (!TestHelpers.isInLauncherProcess()) return;
|
|
||||||
final Application app =
|
|
||||||
(Application) InstrumentationRegistry.getTargetContext().getApplicationContext();
|
|
||||||
app.registerActivityLifecycleCallbacks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getActivitiesCreated() {
|
|
||||||
return mActivitiesCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
|
||||||
mActivities.put(activity, true);
|
|
||||||
++mActivitiesCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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() {
|
|
||||||
for (Activity activity : mActivities.keySet()) {
|
|
||||||
if (activity.isDestroyed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mActivities.size() <= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getActivitiesList() {
|
|
||||||
return mActivities.keySet().stream().map(a -> a.getClass().getSimpleName())
|
|
||||||
.collect(Collectors.joining(","));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1498,6 +1498,30 @@ public final class LauncherInstrumentation {
|
||||||
getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
|
getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String[] getActivities() {
|
||||||
|
return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES)
|
||||||
|
.getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootedActivitiesList() {
|
||||||
|
return String.join(", ", getActivities());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean noLeakedActivities() {
|
||||||
|
final String[] activities = getActivities();
|
||||||
|
for (String activity : activities) {
|
||||||
|
if (activity.contains("(destroyed)")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activities.length <= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActivitiesCreated() {
|
||||||
|
return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT)
|
||||||
|
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
public Closable eventsCheck() {
|
public Closable eventsCheck() {
|
||||||
Assert.assertTrue("Nested event checking", mEventChecker == null);
|
Assert.assertTrue("Nested event checking", mEventChecker == null);
|
||||||
disableSensorRotation();
|
disableSensorRotation();
|
||||||
|
|
Loading…
Reference in New Issue