From 7152d6e2f8bea866e3bd5397b882d5098bed7d8b Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Mon, 4 Apr 2022 13:01:06 -0700 Subject: [PATCH] Update test infra, remove deprecated hacks --- gradle/libs.versions.toml | 3 +- .../leakcanary-android-instrumentation.api | 21 --- .../build.gradle | 2 - .../FailAnnotatedTestOnLeakRunListenerTest.kt | 38 ---- .../OrchestratedInstrumentationListenerSpy.kt | 20 -- .../FailAnnotatedTestOnLeakRunListener.kt | 25 --- .../main/java/leakcanary/FailTestOnLeak.kt | 16 -- .../leakcanary/FailTestOnLeakRunListener.kt | 175 ------------------ .../InstrumentationTestResultPublisher.kt | 30 --- .../OrchestratorTestResultPublisher.kt | 75 -------- .../internal/TestResultPublisher.kt | 37 ---- 11 files changed, 1 insertion(+), 441 deletions(-) delete mode 100644 leakcanary-android-instrumentation/src/androidTest/java/leakcanary/FailAnnotatedTestOnLeakRunListenerTest.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/androidx/test/orchestrator/instrumentationlistener/OrchestratedInstrumentationListenerSpy.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/FailAnnotatedTestOnLeakRunListener.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeak.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeakRunListener.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/internal/InstrumentationTestResultPublisher.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/internal/OrchestratorTestResultPublisher.kt delete mode 100644 leakcanary-android-instrumentation/src/main/java/leakcanary/internal/TestResultPublisher.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08398919fe..35a2d2fc76 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,8 +43,7 @@ androidX-startup = { module = "androidx.startup:startup-runtime", version = "1.0 androidX-test-core = { module = "androidx.test:core", version = "1.4.0" } androidX-test-rules = { module = "androidx.test:rules", version.ref = "androidXTest" } # Exposed transitively, avoid increasing -androidX-test-runner = { module = "androidx.test:runner", version = "1.1.0" } -androidX-test-runner-internal = { module = "androidx.test:runner", version = "1.4.0" } +androidX-test-runner = { module = "androidx.test:runner", version = "1.4.0" } androidX-test-orchestrator = { module = "androidx.test:orchestrator", version = "1.4.1" } androidX-test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.4.0" } androidX-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidXJunit" } diff --git a/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api b/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api index 38ddff665e..edaf6561aa 100644 --- a/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api +++ b/leakcanary-android-instrumentation/api/leakcanary-android-instrumentation.api @@ -21,27 +21,6 @@ public final class leakcanary/DetectLeaksAssert$Companion { public final fun update (Lleakcanary/DetectLeaksAssert;)V } -public final class leakcanary/FailAnnotatedTestOnLeakRunListener : leakcanary/FailTestOnLeakRunListener { - public fun ()V -} - -public abstract interface annotation class leakcanary/FailTestOnLeak : java/lang/annotation/Annotation { -} - -public class leakcanary/FailTestOnLeakRunListener : org/junit/runner/notification/RunListener { - public fun ()V - protected final fun failTest (Ljava/lang/String;)V - protected fun onAnalysisPerformed (Lshark/HeapAnalysis;)V - protected fun skipLeakDetectionReason (Lorg/junit/runner/Description;)Ljava/lang/String; - public fun testAssumptionFailure (Lorg/junit/runner/notification/Failure;)V - public fun testFailure (Lorg/junit/runner/notification/Failure;)V - public fun testFinished (Lorg/junit/runner/Description;)V - public fun testIgnored (Lorg/junit/runner/Description;)V - public fun testRunFinished (Lorg/junit/runner/Result;)V - public fun testRunStarted (Lorg/junit/runner/Description;)V - public fun testStarted (Lorg/junit/runner/Description;)V -} - public abstract interface class leakcanary/HeapAnalysisReporter { public abstract fun reportHeapAnalysis (Lshark/HeapAnalysis;)V } diff --git a/leakcanary-android-instrumentation/build.gradle b/leakcanary-android-instrumentation/build.gradle index 0a621762e8..096d9153c0 100644 --- a/leakcanary-android-instrumentation/build.gradle +++ b/leakcanary-android-instrumentation/build.gradle @@ -10,8 +10,6 @@ dependencies { implementation libs.androidX.test.runner implementation libs.kotlin.stdlib - // Using a more recent version (without enforcing that on consumers) - androidTestImplementation libs.androidX.test.runner.internal // AppWatcher auto installer for running tests androidTestImplementation project(':leakcanary-object-watcher-android') // Plumber auto installer for running tests diff --git a/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/FailAnnotatedTestOnLeakRunListenerTest.kt b/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/FailAnnotatedTestOnLeakRunListenerTest.kt deleted file mode 100644 index 8f93f29726..0000000000 --- a/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/FailAnnotatedTestOnLeakRunListenerTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package leakcanary - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test -import org.junit.runner.Description - -/** - * Tests that the [FailAnnotatedTestOnLeakRunListenerTest] only detect leaks - * in instrumentation tests when the correct annotation is used - */ -class FailAnnotatedTestOnLeakRunListenerTest { - - @FailTestOnLeak - @Test - fun detectsLeak() { - val annotation = - javaClass.getMethod("detectsLeak").getAnnotation(FailTestOnLeak::class.java) - val description = Description.createTestDescription("test", "Test mechanism", annotation) - val listener = FailAnnotatedTestOnLeakRunListener() - val method = - listener.javaClass.getDeclaredMethod("skipLeakDetectionReason", Description::class.java) - method.isAccessible = true - val result = method.invoke(listener, description) - assertNull(result) - } - - @Test - fun skipsLeakDetectionWithoutAnnotation() { - val description = Description.createTestDescription("test", "Test mechanism") - val listener = FailAnnotatedTestOnLeakRunListener() - val method = - listener.javaClass.getDeclaredMethod("skipLeakDetectionReason", Description::class.java) - method.isAccessible = true - val result = method.invoke(listener, description) - assertEquals("test is not annotated with @FailTestOnLeak", result) - } -} \ No newline at end of file diff --git a/leakcanary-android-instrumentation/src/main/java/androidx/test/orchestrator/instrumentationlistener/OrchestratedInstrumentationListenerSpy.kt b/leakcanary-android-instrumentation/src/main/java/androidx/test/orchestrator/instrumentationlistener/OrchestratedInstrumentationListenerSpy.kt deleted file mode 100644 index 9f67ec060d..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/androidx/test/orchestrator/instrumentationlistener/OrchestratedInstrumentationListenerSpy.kt +++ /dev/null @@ -1,20 +0,0 @@ -package androidx.test.orchestrator.instrumentationlistener - -import android.os.Bundle -import androidx.test.orchestrator.callback.OrchestratorCallback - -internal fun OrchestratedInstrumentationListener.delegateSendTestNotification( - onSendTestNotification: (Bundle, (Bundle) -> Unit) -> Unit -) { - val realCallback = odoCallback - - val sendTestNotificationCallback: (Bundle) -> Unit = { bundle -> - realCallback.sendTestNotification(bundle) - } - - odoCallback = object : OrchestratorCallback by realCallback { - override fun sendTestNotification(bundle: Bundle) { - onSendTestNotification(bundle, sendTestNotificationCallback) - } - } -} diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailAnnotatedTestOnLeakRunListener.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/FailAnnotatedTestOnLeakRunListener.kt deleted file mode 100644 index 9441c70540..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailAnnotatedTestOnLeakRunListener.kt +++ /dev/null @@ -1,25 +0,0 @@ -package leakcanary - -import org.junit.runner.Description -import org.junit.runner.notification.RunListener - -/** - * Deprecated because this relies on hacks built on top of AndroidX Test internals which keep - * changing. Use [LeakAssertions] instead. - * - * A JUnit [RunListener] extending [FailTestOnLeakRunListener] to detecting memory - * leaks in Android instrumentation tests only when the [FailTestOnLeak] annotation - * is used. - * - * @see FailTestOnLeak - */ -@Deprecated("Use LeakAssertions instead") -class FailAnnotatedTestOnLeakRunListener : FailTestOnLeakRunListener() { - - override fun skipLeakDetectionReason(description: Description) = - if (description.getAnnotation(FailTestOnLeak::class.java) != null) { - null - } else { - "test is not annotated with @FailTestOnLeak" - } -} diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeak.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeak.kt deleted file mode 100644 index 0d274c839a..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeak.kt +++ /dev/null @@ -1,16 +0,0 @@ -package leakcanary - -/** - * Deprecated because this relies on hacks built on top of AndroidX Test internals which keep - * changing. Use [LeakAssertions] instead. - * - * An [Annotation] class to be used in conjunction with [FailAnnotatedTestOnLeakRunListener] - * for detecting memory leaks. When using [FailAnnotatedTestOnLeakRunListener], the tests - * should be annotated with this class in order for the listener to detect memory leaks. - * - * @see FailAnnotatedTestOnLeakRunListener - */ -@Deprecated("Use LeakAssertions instead") -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.FUNCTION) -annotation class FailTestOnLeak diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeakRunListener.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeakRunListener.kt deleted file mode 100644 index ba4ba1d5c8..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/FailTestOnLeakRunListener.kt +++ /dev/null @@ -1,175 +0,0 @@ -package leakcanary - -import android.app.Activity -import android.app.Application -import android.app.Application.ActivityLifecycleCallbacks -import android.os.Bundle -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit.SECONDS -import leakcanary.InstrumentationLeakDetector.Result.AnalysisPerformed -import leakcanary.internal.TestResultPublisher -import leakcanary.internal.friendly.noOpDelegate -import org.junit.runner.Description -import org.junit.runner.Result -import org.junit.runner.notification.Failure -import org.junit.runner.notification.RunListener -import shark.HeapAnalysis -import shark.HeapAnalysisFailure -import shark.HeapAnalysisSuccess -import shark.SharkLog - -/** - * Deprecated because this relies on hacks built on top of AndroidX Test internals which keep - * changing. Use [LeakAssertions] instead. - * - * A JUnit [RunListener] that uses [InstrumentationLeakDetector] to detect memory leaks in Android - * instrumentation tests. It waits for the end of a test, and if the test succeeds then it will - * look for retained objects, trigger a heap dump if needed and perform an analysis. - * - * [FailTestOnLeakRunListener] can be subclassed to override [skipLeakDetectionReason] and - * [onAnalysisPerformed] - * - * @see InstrumentationLeakDetector - */ -@Deprecated("Use LeakAssertions instead") -open class FailTestOnLeakRunListener : RunListener() { - private var _currentTestDescription: Description? = null - private val currentTestDescription: Description - get() = _currentTestDescription!! - - private var skipLeakDetectionReason: String? = null - - private lateinit var testResultPublisher: TestResultPublisher - - @Volatile - private var allActivitiesDestroyedLatch: CountDownLatch? = null - - override fun testRunStarted(description: Description) { - testResultPublisher = TestResultPublisher.install() - trackActivities() - } - - private fun trackActivities() { - val instrumentation = getInstrumentation()!! - val application = instrumentation.targetContext.applicationContext as Application - application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks by noOpDelegate() { - - var activitiesWaitingForDestroyed = 0 - - override fun onActivityCreated( - activity: Activity, - savedInstanceState: Bundle? - ) { - if (activitiesWaitingForDestroyed == 0) { - allActivitiesDestroyedLatch = CountDownLatch(1) - } - activitiesWaitingForDestroyed++ - } - - override fun onActivityDestroyed(activity: Activity) { - activitiesWaitingForDestroyed-- - if (activitiesWaitingForDestroyed == 0) { - allActivitiesDestroyedLatch!!.countDown() - } - } - }) - } - - override fun testRunFinished(result: Result) { - } - - override fun testStarted(description: Description) { - _currentTestDescription = description - skipLeakDetectionReason = skipLeakDetectionReason(description) - if (skipLeakDetectionReason != null) { - return - } - } - - /** - * Can be overridden to skip leak detection based on the description provided when a test - * is started. Return null to continue leak detection, or a string describing the reason for - * skipping otherwise. - */ - protected open fun skipLeakDetectionReason(description: Description): String? { - return null - } - - override fun testFailure(failure: Failure) { - skipLeakDetectionReason = "failed" - } - - override fun testIgnored(description: Description) { - skipLeakDetectionReason = "was ignored" - } - - override fun testAssumptionFailure(failure: Failure) { - skipLeakDetectionReason = "had an assumption failure" - } - - override fun testFinished(description: Description) { - if (skipLeakDetectionReason == null) { - detectLeaks() - } else { - SharkLog.d { "Skipping leak detection because the test $skipLeakDetectionReason" } - skipLeakDetectionReason = null - } - AppWatcher.objectWatcher.clearWatchedObjects() - _currentTestDescription = null - testResultPublisher.publishTestFinished() - } - - private fun detectLeaks() { - val allActivitiesDestroyed = allActivitiesDestroyedLatch?.await(2, SECONDS) ?: true - if (!allActivitiesDestroyed) { - SharkLog.d { "Leak detection proceeding with some activities still not in destroyed state" } - } - - val leakDetector = InstrumentationLeakDetector() - val result = leakDetector.detectLeaks() - - if (result is AnalysisPerformed) { - onAnalysisPerformed(heapAnalysis = result.heapAnalysis) - } else { - SharkLog.d { "No heap analysis performed" } - } - } - - /** - * Called when a heap analysis has been performed and a result is available. - * - * The default implementation call [failTest] if the [heapAnalysis] failed or if - * [HeapAnalysisSuccess.applicationLeaks] is not empty. - */ - protected open fun onAnalysisPerformed(heapAnalysis: HeapAnalysis) { - when (heapAnalysis) { - is HeapAnalysisFailure -> { - failTest( - "$currentTestDescription failed because heap analysis failed:\n" + Log.getStackTraceString( - heapAnalysis.exception - ) - ) - } - is HeapAnalysisSuccess -> { - val applicationLeaks = heapAnalysis.applicationLeaks - if (applicationLeaks.isNotEmpty()) { - failTest( - "$currentTestDescription failed because application memory leaks were detected:\n$heapAnalysis" - ) - } else { - SharkLog.d { "Heap analysis found 0 application leaks:\n$heapAnalysis" } - } - } - } - } - - /** - * Reports that the test has failed, with the provided [trace]. - */ - protected fun failTest(trace: String) { - SharkLog.d { trace } - testResultPublisher.publishTestFailure(currentTestDescription, trace) - } -} diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/InstrumentationTestResultPublisher.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/InstrumentationTestResultPublisher.kt deleted file mode 100644 index a4d01964d1..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/InstrumentationTestResultPublisher.kt +++ /dev/null @@ -1,30 +0,0 @@ -package leakcanary.internal - -import android.app.Instrumentation.REPORT_KEY_IDENTIFIER -import android.os.Bundle -import androidx.test.internal.runner.listener.InstrumentationResultPrinter.REPORT_KEY_NAME_CLASS -import androidx.test.internal.runner.listener.InstrumentationResultPrinter.REPORT_KEY_NAME_TEST -import androidx.test.internal.runner.listener.InstrumentationResultPrinter.REPORT_KEY_STACK -import androidx.test.internal.runner.listener.InstrumentationResultPrinter.REPORT_VALUE_RESULT_FAILURE -import androidx.test.platform.app.InstrumentationRegistry -import leakcanary.FailTestOnLeakRunListener -import org.junit.runner.Description - -internal class InstrumentationTestResultPublisher : TestResultPublisher { - override fun publishTestFinished() { - } - - override fun publishTestFailure( - description: Description, - trace: String - ) { - val bundle = Bundle() - bundle.putString(REPORT_KEY_IDENTIFIER, FailTestOnLeakRunListener::class.java.name) - bundle.putString(REPORT_KEY_NAME_CLASS, description.className) - bundle.putString(REPORT_KEY_NAME_TEST, description.methodName) - bundle.putString(REPORT_KEY_STACK, trace) - - val instrumentation = InstrumentationRegistry.getInstrumentation() - instrumentation.sendStatus(REPORT_VALUE_RESULT_FAILURE, bundle) - } -} diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/OrchestratorTestResultPublisher.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/OrchestratorTestResultPublisher.kt deleted file mode 100644 index 65c54f4c4b..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/OrchestratorTestResultPublisher.kt +++ /dev/null @@ -1,75 +0,0 @@ -package leakcanary.internal - -import android.os.Bundle -import androidx.test.orchestrator.instrumentationlistener.OrchestratedInstrumentationListener -import androidx.test.orchestrator.instrumentationlistener.delegateSendTestNotification -import androidx.test.orchestrator.junit.ParcelableDescription -import androidx.test.orchestrator.junit.ParcelableFailure -import androidx.test.orchestrator.junit.ParcelableResult -import androidx.test.orchestrator.listeners.OrchestrationListenerManager.KEY_TEST_EVENT -import androidx.test.orchestrator.listeners.OrchestrationListenerManager.TestEvent.TEST_FAILURE -import androidx.test.orchestrator.listeners.OrchestrationListenerManager.TestEvent.TEST_FINISHED -import androidx.test.orchestrator.listeners.OrchestrationListenerManager.TestEvent.TEST_RUN_FINISHED -import org.junit.runner.Description - -internal class OrchestratorTestResultPublisher(listener: OrchestratedInstrumentationListener) : - TestResultPublisher { - - private var sendTestFinished: (() -> Unit)? = null - - private var failureBundle: Bundle? = null - - private var receivedTestFinished: Boolean = false - - init { - val failures = mutableListOf() - listener.delegateSendTestNotification { testEventBundle, sendTestNotification -> - - when (testEventBundle.getString(KEY_TEST_EVENT)) { - TEST_FINISHED.toString() -> { - sendTestFinished = { - failureBundle?.let { failureBundle -> - failures += failureBundle.get("failure") as ParcelableFailure - sendTestNotification(failureBundle) - } - sendTestNotification(testEventBundle) - // reset for next test if any. - sendTestFinished = null - failureBundle = null - receivedTestFinished = false - } - if (receivedTestFinished) { - sendTestFinished!!.invoke() - } - } - TEST_RUN_FINISHED.toString() -> { - if (failures.isNotEmpty()) { - val result = testEventBundle.get("result") as ParcelableResult - result.failures += failures - } - sendTestNotification(testEventBundle) - } - else -> sendTestNotification(testEventBundle) - } - } - } - - override fun publishTestFinished() { - receivedTestFinished = true - sendTestFinished?.invoke() - } - - override fun publishTestFailure( - description: Description, - trace: String - ) { - val result = Bundle() - val failure = ParcelableFailure( - ParcelableDescription(description), - RuntimeException(trace) - ) - result.putParcelable("failure", failure) - result.putString(KEY_TEST_EVENT, TEST_FAILURE.toString()) - this.failureBundle = result - } -} diff --git a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/TestResultPublisher.kt b/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/TestResultPublisher.kt deleted file mode 100644 index d7489b9889..0000000000 --- a/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/TestResultPublisher.kt +++ /dev/null @@ -1,37 +0,0 @@ -package leakcanary.internal - -import androidx.test.orchestrator.instrumentationlistener.OrchestratedInstrumentationListener -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.runner.AndroidJUnitRunner -import org.junit.runner.Description -import shark.SharkLog - -internal interface TestResultPublisher { - - fun publishTestFinished() - - fun publishTestFailure( - description: Description, - trace: String - ) - - companion object { - fun install(): TestResultPublisher { - val instrumentation = InstrumentationRegistry.getInstrumentation() - val orchestratorListener = if (instrumentation is AndroidJUnitRunner) { - AndroidJUnitRunner::class.java.getDeclaredField("orchestratorListener") - .run { - isAccessible = true - get(instrumentation) as OrchestratedInstrumentationListener? - } - } else null - return if (orchestratorListener != null) { - SharkLog.d { "Android Test Orchestrator detected, failures will be sent via binder callback" } - OrchestratorTestResultPublisher(orchestratorListener) - } else { - SharkLog.d { "Failures will be sent via Instrumentation.sendStatus()" } - InstrumentationTestResultPublisher() - } - } - } -}