Make thread leak error message more readable
This change only outputs thread count differences instead of
listing all threads. Additionally, it ignores threads with 1
count, which effectively filter out threads created
by the test runner or other system components, such as
hwuiTask*, queued-work-looper, SurfaceSyncGroupTimer,
RenderThread, and Time-limited test.
Sample Output:
[1122/3060] android.net.connectivity.com.android.server.CSLocalAgentCreationTests#ThreadLeakMonitor: FAILED (1ms)
STACKTRACE:
java.lang.IllegalStateException: Unexpected thread changes: removed=[] added=[] updated=[TestAlarmManager=25,CSTestHandler=25]
Test: atest ConnectivityCoverageTests
(with shouldThreadLeakFailTest set to true)
Bug: 310581973
Bug: 307693729
Change-Id: I085799ebbd69c29f833335684de208105302e012
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 1ba83ca..10accd4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -17,9 +17,9 @@
package com.android.testutils
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import java.lang.IllegalStateException
import java.lang.reflect.Modifier
import org.junit.runner.Description
import org.junit.runner.Runner
@@ -110,10 +110,19 @@
notifier.fireTestStarted(leakMonitorDesc)
val threadCountsAfterTest = getAllThreadNameCounts()
- if (threadCountsBeforeTest != threadCountsAfterTest) {
+ // TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
+ val threadsDiff = CompareOrUpdateResult(
+ threadCountsBeforeTest.entries,
+ threadCountsAfterTest.entries
+ ) { it.key }
+ // Ignore removed threads, which typically are generated by previous tests.
+ // Because this is in the threadsDiff.updated member, for sure there is a
+ // corresponding key in threadCountsBeforeTest.
+ val increasedThreads = threadsDiff.updated
+ .filter { threadCountsBeforeTest[it.key]!! < it.value }
+ if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
notifier.fireTestFailure(Failure(leakMonitorDesc,
- IllegalStateException("Expected threads: $threadCountsBeforeTest " +
- "but got: $threadCountsAfterTest")))
+ IllegalStateException("Unexpected thread changes: $threadsDiff")))
}
notifier.fireTestFinished(leakMonitorDesc)
}
@@ -121,9 +130,13 @@
private fun getAllThreadNameCounts(): Map<String, Int> {
// Get the counts of threads in the group per name.
// Filter system thread groups.
+ // Also ignore threads with 1 count, this effectively filtered out threads created by the
+ // test runner or other system components. e.g. hwuiTask*, queued-work-looper,
+ // SurfaceSyncGroupTimer, RenderThread, Time-limited test, etc.
return Thread.getAllStackTraces().keys
.filter { it.threadGroup?.name != "system" }
.groupingBy { it.name }.eachCount()
+ .filter { it.value != 1 }
}
override fun getDescription(): Description {