From ed0fab03568ab1f31c6efb8cff3ee7bb4cf7ecee Mon Sep 17 00:00:00 2001 From: markchien Date: Tue, 26 Apr 2022 16:33:47 +0800 Subject: [PATCH] Add a test to identify memory leak in ConnectivityManager The sInstance static instance which self reference to ConnectivityManager which holds a reference to a ConnectivityManager instance causes the Context referenced by that instance to never be GCed. Bug: 202978965 Test: atest ConnectivityManagerTest Change-Id: I0227f63dbc27688ea5f4ef9275fd0f9c247ad14c --- .../android/net/ConnectivityManagerTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java index f324630bd2..f64ae5816a 100644 --- a/tests/unit/java/android/net/ConnectivityManagerTest.java +++ b/tests/unit/java/android/net/ConnectivityManagerTest.java @@ -72,6 +72,7 @@ import android.os.Process; import androidx.test.filters.SmallTest; +import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; @@ -82,6 +83,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.lang.ref.WeakReference; + @RunWith(DevSdkIgnoreRunner.class) @SmallTest @DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R) @@ -461,4 +464,50 @@ public class ConnectivityManagerTest { } fail("expected exception of type " + throwableType); } + + private static class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Context getApplicationContext() { + return mock(Context.class); + } + } + + private WeakReference makeConnectivityManagerAndReturnContext() { + // Mockito may have an internal reference to the mock, creating MockContext for testing. + final Context c = new MockContext(mock(Context.class)); + + new ConnectivityManager(c, mService); + + return new WeakReference<>(c); + } + + private void forceGC() { + // First GC ensures that objects are collected for finalization, then second GC ensures + // they're garbage-collected after being finalized. + System.gc(); + System.runFinalization(); + System.gc(); + } + + @Test + public void testConnectivityManagerDoesNotLeakContext() throws Exception { + final WeakReference ref = makeConnectivityManagerAndReturnContext(); + + final int attempts = 100; + final long waitIntervalMs = 50; + for (int i = 0; i < attempts; i++) { + forceGC(); + if (ref.get() == null) break; + + Thread.sleep(waitIntervalMs); + } + + // TODO: fix memory leak then assertNull here. + assertNotNull("Couldn't find the Context leak in ConnectivityManager after " + attempts + + " attempts", ref.get()); + } }