diff options
Diffstat (limited to 'tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt')
-rw-r--r-- | tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt | 102 |
1 files changed, 92 insertions, 10 deletions
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt index 0c6553998d..ccbae4fb0c 100644 --- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt +++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt @@ -20,9 +20,21 @@ import android.app.Application import android.media.permission.SafeCloseable import android.os.Bundle import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry import com.android.app.viewcapture.SimpleViewCapture import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR +import com.android.app.viewcapture.data.ExportedData +import com.android.launcher3.tapl.TestHelpers import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter +import com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT +import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer +import java.io.BufferedOutputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStreamWriter +import java.util.function.Supplier +import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -33,28 +45,33 @@ import org.junit.runners.model.Statement * * This rule will not work in OOP tests that don't have access to the activity under test. */ -class ViewCaptureRule : TestRule { - val viewCapture = SimpleViewCapture("test-view-capture") +class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : TestRule { + private val viewCapture = SimpleViewCapture("test-view-capture") + var viewCaptureData: ExportedData? = null + private set override fun apply(base: Statement, description: Description): Statement { + // Skip view capture collection in Launcher3 tests to avoid hidden API check exception. + if ( + "com.android.launcher3.tests" == + InstrumentationRegistry.getInstrumentation().context.packageName + ) + return base + return object : Statement() { override fun evaluate() { + viewCaptureData = null val windowListenerCloseables = mutableListOf<SafeCloseable>() + startCapturingExistingActivity(windowListenerCloseables) + val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter { override fun onActivityCreated(activity: Activity, bundle: Bundle?) { - super.onActivityCreated(activity, bundle) - windowListenerCloseables.add( - viewCapture.startCapture( - activity.window.decorView, - "${description.testClass?.simpleName}.${description.methodName}" - ) - ) + startCapture(windowListenerCloseables, activity) } override fun onActivityDestroyed(activity: Activity) { - super.onActivityDestroyed(activity) viewCapture.stopCapture(activity.window.decorView) } } @@ -67,6 +84,9 @@ class ViewCaptureRule : TestRule { } finally { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) + viewCaptureData = + viewCapture.getExportedData(ApplicationProvider.getApplicationContext()) + // Clean up ViewCapture references here rather than in onActivityDestroyed so // test code can access view hierarchy capture. onActivityDestroyed would delete // view capture data before FailureWatcher could output it as a test artifact. @@ -74,7 +94,69 @@ class ViewCaptureRule : TestRule { // is removed while onDraw is running, resulting in an IllegalStateException. MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) } } + + analyzeViewCapture(description) } + + private fun startCapturingExistingActivity( + windowListenerCloseables: MutableCollection<SafeCloseable> + ) { + val alreadyOpenActivity = alreadyOpenActivitySupplier.get() + if (alreadyOpenActivity != null) { + startCapture(windowListenerCloseables, alreadyOpenActivity) + } + } + + private fun startCapture( + windowListenerCloseables: MutableCollection<SafeCloseable>, + activity: Activity + ) { + windowListenerCloseables.add( + viewCapture.startCapture( + activity.window.decorView, + "${description.testClass?.simpleName}.${description.methodName}" + ) + ) + } + } + } + + private fun analyzeViewCapture(description: Description) { + // OOP tests don't produce ViewCapture data + if (!TestHelpers.isInLauncherProcess()) return + + // Due to flakiness of ViewCapture verifier, don't run the check in presubmit + if (TestStabilityRule.getRunFlavor() != PLATFORM_POSTSUBMIT) return + + var frameCount = 0 + for (i in 0 until viewCaptureData!!.windowDataCount) { + frameCount += viewCaptureData!!.getWindowData(i).frameDataCount + } + assertTrue("Empty ViewCapture data", frameCount > 0) + + val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData) + if (!anomalies.isEmpty()) { + val diagFile = FailureWatcher.diagFile(description, "ViewAnomalies", "txt") + try { + OutputStreamWriter(BufferedOutputStream(FileOutputStream(diagFile))).use { writer -> + writer.write("View animation anomalies detected.\r\n") + writer.write( + "To suppress an anomaly for a view, add its full path to the PATHS_TO_IGNORE list in the corresponding AnomalyDetector.\r\n" + ) + writer.write("List of views with animation anomalies:\r\n") + + for ((viewPath, message) in anomalies) { + writer.write("View: $viewPath\r\n $message\r\n") + } + } + } catch (ex: IOException) { + throw RuntimeException(ex) + } + + val (viewPath, message) = anomalies.entries.first() + fail( + "${anomalies.size} view(s) had animation anomalies during the test, including view: $viewPath: $message\r\nSee ${diagFile.name} for details." + ) } } } |