Add warnings for leaked receivers in tests
This CL adds a way to track BroadcastReceivers that are leaking in test cases that use SysuiTestCase (all of SystemUITests). Every receiver that is registered with Context is recorded and those that are not unregistered are logged as a Warning. After each test, receivers are unregistered so they don't polute following tests. Additionally, if a BroadcastDispatcher is instantiated, a Fake is provided that will also track leaked receivers and log them as Info (as BroadcastDispatcher will only keep WeakReferences). This is a separate tracker done directly in SysuiTestableContext instead of using LeakCheckedTest or a LeakChecker as that is usually pretty stringent (it will fail the test). Test: SystemUITests Bug: 151614195 Change-Id: I11afb49ce3bbc51d98fa069ba4074c852d07bcce
This commit is contained in:
@@ -94,7 +94,7 @@ open class BroadcastDispatcher @Inject constructor (
|
||||
@Deprecated(message = "Replacing Handler for Executor in SystemUI",
|
||||
replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
|
||||
@JvmOverloads
|
||||
fun registerReceiverWithHandler(
|
||||
open fun registerReceiverWithHandler(
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
handler: Handler,
|
||||
@@ -118,7 +118,7 @@ open class BroadcastDispatcher @Inject constructor (
|
||||
* categories or the filter has no actions.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun registerReceiver(
|
||||
open fun registerReceiver(
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
executor: Executor? = context.mainExecutor,
|
||||
@@ -149,7 +149,7 @@ open class BroadcastDispatcher @Inject constructor (
|
||||
*
|
||||
* @param receiver The receiver to unregister. It will be unregistered for all users.
|
||||
*/
|
||||
fun unregisterReceiver(receiver: BroadcastReceiver) {
|
||||
open fun unregisterReceiver(receiver: BroadcastReceiver) {
|
||||
handler.obtainMessage(MSG_REMOVE_RECEIVER, receiver).sendToTarget()
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ open class BroadcastDispatcher @Inject constructor (
|
||||
* @param receiver The receiver to unregister. It will be unregistered for all users.
|
||||
* @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL].
|
||||
*/
|
||||
fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
|
||||
open fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
|
||||
handler.obtainMessage(MSG_REMOVE_RECEIVER_FOR_USER, user.identifier, 0, receiver)
|
||||
.sendToTarget()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.systemui;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -32,7 +33,10 @@ import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
|
||||
import com.android.systemui.classifier.FalsingManagerFake;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -60,11 +64,14 @@ public abstract class SysuiTestCase {
|
||||
new DexmakerShareClassLoaderRule();
|
||||
public TestableDependency mDependency;
|
||||
private Instrumentation mRealInstrumentation;
|
||||
private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
|
||||
|
||||
@Before
|
||||
public void SysuiSetup() throws Exception {
|
||||
SystemUIFactory.createFromConfig(mContext);
|
||||
mDependency = new TestableDependency(mContext);
|
||||
mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Handler.class),
|
||||
mock(Looper.class), mock(DumpManager.class));
|
||||
|
||||
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||
Instrumentation inst = spy(mRealInstrumentation);
|
||||
@@ -77,12 +84,18 @@ public abstract class SysuiTestCase {
|
||||
"SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
|
||||
});
|
||||
InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
|
||||
// Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
|
||||
// record receivers registered. They are not actually leaked as they are kept just as a weak
|
||||
// reference and are never sent to the Context. This will also prevent a real
|
||||
// BroadcastDispatcher from actually registering receivers.
|
||||
mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
|
||||
// A lot of tests get the FalsingManager, often via several layers of indirection.
|
||||
// None of them actually need it.
|
||||
mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
|
||||
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
|
||||
|
||||
// TODO: b/151614195 investigate root cause of needing this mock dependency
|
||||
// A lot of tests get the LocalBluetoothManager, often via several layers of indirection.
|
||||
// None of them actually need it.
|
||||
mDependency.injectMockDependency(LocalBluetoothManager.class);
|
||||
}
|
||||
|
||||
@@ -95,6 +108,8 @@ public abstract class SysuiTestCase {
|
||||
}
|
||||
disallowTestableLooperAsMainThread();
|
||||
SystemUIFactory.cleanup();
|
||||
mContext.cleanUpReceivers(this.getClass().getSimpleName());
|
||||
mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,12 +14,24 @@
|
||||
|
||||
package com.android.systemui;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.testing.LeakCheck;
|
||||
import android.testing.TestableContext;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class SysuiTestableContext extends TestableContext {
|
||||
|
||||
private Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
|
||||
|
||||
public SysuiTestableContext(Context base) {
|
||||
super(base);
|
||||
setTheme(R.style.Theme_SystemUI);
|
||||
@@ -40,4 +52,42 @@ public class SysuiTestableContext extends TestableContext {
|
||||
new SysuiTestableContext(getBaseContext().createDisplayContext(display));
|
||||
return context;
|
||||
}
|
||||
|
||||
public void cleanUpReceivers(String testName) {
|
||||
Set<BroadcastReceiver> copy = new ArraySet<>(mRegisteredReceivers);
|
||||
for (BroadcastReceiver r : copy) {
|
||||
try {
|
||||
unregisterReceiver(r);
|
||||
Log.w(testName, "Receiver not unregistered from Context: " + r);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Nothing to do here. Somehow it got unregistered.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
|
||||
mRegisteredReceivers.add(receiver);
|
||||
return super.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
|
||||
String broadcastPermission, Handler scheduler) {
|
||||
mRegisteredReceivers.add(receiver);
|
||||
return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
|
||||
IntentFilter filter, String broadcastPermission, Handler scheduler) {
|
||||
mRegisteredReceivers.add(receiver);
|
||||
return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterReceiver(BroadcastReceiver receiver) {
|
||||
mRegisteredReceivers.remove(receiver);
|
||||
super.unregisterReceiver(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.systemui.broadcast
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.IntentFilter
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.UserHandle
|
||||
import android.util.ArraySet
|
||||
import android.util.Log
|
||||
import com.android.systemui.SysuiTestableContext
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class FakeBroadcastDispatcher(
|
||||
context: SysuiTestableContext,
|
||||
handler: Handler,
|
||||
looper: Looper,
|
||||
dumpManager: DumpManager
|
||||
) : BroadcastDispatcher(context, handler, looper, dumpManager) {
|
||||
|
||||
private val registeredReceivers = ArraySet<BroadcastReceiver>()
|
||||
|
||||
override fun registerReceiverWithHandler(
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
handler: Handler,
|
||||
user: UserHandle
|
||||
) {
|
||||
registeredReceivers.add(receiver)
|
||||
}
|
||||
|
||||
override fun registerReceiver(
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
executor: Executor?,
|
||||
user: UserHandle
|
||||
) {
|
||||
registeredReceivers.add(receiver)
|
||||
}
|
||||
|
||||
override fun unregisterReceiver(receiver: BroadcastReceiver) {
|
||||
registeredReceivers.remove(receiver)
|
||||
}
|
||||
|
||||
override fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
|
||||
registeredReceivers.remove(receiver)
|
||||
}
|
||||
|
||||
fun cleanUpReceivers(testName: String) {
|
||||
registeredReceivers.forEach {
|
||||
Log.i(testName, "Receiver not unregistered from dispatcher: $it")
|
||||
}
|
||||
registeredReceivers.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user