Bug 1486596 - Add tests to ensure GeckoSession can be collected by the GC r=jchen
authorJames Willcox <snorp@snorp.net>
Thu, 30 Aug 2018 13:18:57 -0500
changeset 487743 c2b5dfcbd692d0c713ace2076d00b8d973c9a400
parent 487742 7e5bfc676ff76850c341dc0e17787cc761c1b49d
child 487744 fa568bd2149fb9f324fab80f8511063bb1819a81
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewersjchen
bugs1486596
milestone64.0a1
Bug 1486596 - Add tests to ensure GeckoSession can be collected by the GC r=jchen
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -6,31 +6,42 @@ package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.GeckoView
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.util.Callbacks
+import org.mozilla.geckoview.test.util.UiThreadUtils
 
+import android.os.Debug
 import android.os.Parcelable
+import android.os.SystemClock
 import android.support.test.InstrumentationRegistry
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
+import android.util.Log
 import android.util.SparseArray
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.File
+import java.io.IOException
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class SessionLifecycleTest : BaseSessionTest() {
+    companion object {
+        val LOGTAG = "SessionLifecycleTest"
+    }
 
     @Test fun open_interleaved() {
         val session1 = sessionRule.createOpenSession()
         val session2 = sessionRule.createOpenSession()
         session1.close()
         val session3 = sessionRule.createOpenSession()
         session2.close()
         session3.close()
@@ -365,9 +376,72 @@ class SessionLifecycleTest : BaseSession
 
             newSession.close()
             assertThat("New session can be closed", newSession.isOpen, equalTo(false))
         }
 
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
     }
+
+    @Test fun collectClosed() {
+        // We can't use a normal scoped function like `run` because
+        // those are inlined, which leaves a local reference.
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            return QueuedWeakReference<GeckoSession>(GeckoSession())
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    @Test fun collectAfterClose() {
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            val s = GeckoSession()
+            s.open(sessionRule.runtime)
+            s.close()
+            return QueuedWeakReference<GeckoSession>(s)
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    @Test fun collectOpen() {
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            val s = GeckoSession()
+            s.open(sessionRule.runtime)
+            return QueuedWeakReference<GeckoSession>(s)
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    private fun dumpHprof() {
+        try {
+            val dest = File(InstrumentationRegistry.getTargetContext()
+                    .filesDir.parent, "dump.hprof").absolutePath
+            Debug.dumpHprofData(dest)
+            Log.d(LOGTAG, "Dumped hprof to $dest")
+        } catch (e: IOException) {
+            Log.e(LOGTAG, "Failed to dump hprof", e)
+        }
+
+    }
+
+    private fun waitUntilCollected(ref: QueuedWeakReference<*>) {
+        val start = SystemClock.uptimeMillis()
+        while (ref.queue.poll() == null) {
+            val elapsed = SystemClock.uptimeMillis() - start
+            if (elapsed > sessionRule.timeoutMillis) {
+                dumpHprof()
+                throw UiThreadUtils.TimeoutException("Timed out after " + elapsed + "ms")
+            }
+
+            try {
+                UiThreadUtils.loopUntilIdle(100)
+            } catch (e: UiThreadUtils.TimeoutException) {
+            }
+            Runtime.getRuntime().gc()
+        }
+    }
+
+    class QueuedWeakReference<T> @JvmOverloads constructor(obj: T, var queue: ReferenceQueue<T> =
+            ReferenceQueue()) : WeakReference<T>(obj, queue)
 }