diff options
Diffstat (limited to 'tests/src/com/android/launcher3/util/rule')
5 files changed, 108 insertions, 192 deletions
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 7ca6a06ed2..62d70ad1cd 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -8,10 +8,9 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.core.app.ApplicationProvider; import androidx.test.uiautomator.UiDevice; -import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.data.ExportedData; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.ui.AbstractLauncherUiTest; @@ -24,22 +23,21 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.function.Supplier; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class FailureWatcher extends TestWatcher { private static final String TAG = "FailureWatcher"; private static boolean sSavedBugreport = false; - final private UiDevice mDevice; private final LauncherInstrumentation mLauncher; @NonNull - private final ViewCapture mViewCapture; + private final Supplier<ExportedData> mViewCaptureDataSupplier; - public FailureWatcher(UiDevice device, LauncherInstrumentation launcher, - @NonNull ViewCapture viewCapture) { - mDevice = device; + public FailureWatcher(LauncherInstrumentation launcher, + @NonNull Supplier<ExportedData> viewCaptureDataSupplier) { mLauncher = launcher; - mViewCapture = viewCapture; + mViewCaptureDataSupplier = viewCaptureDataSupplier; } @Override @@ -71,7 +69,7 @@ public class FailureWatcher extends TestWatcher { @Override protected void failed(Throwable e, Description description) { - onError(mLauncher, description, e, mViewCapture); + onError(mLauncher, description, e, mViewCaptureDataSupplier); } static File diagFile(Description description, String prefix, String ext) { @@ -86,7 +84,7 @@ public class FailureWatcher extends TestWatcher { } private static void onError(LauncherInstrumentation launcher, Description description, - Throwable e, @Nullable ViewCapture viewCapture) { + Throwable e, @Nullable Supplier<ExportedData> viewCaptureDataSupplier) { final File sceenshot = diagFile(description, "TestScreenshot", "png"); final File hierarchy = diagFile(description, "Hierarchy", "zip"); @@ -103,9 +101,10 @@ public class FailureWatcher extends TestWatcher { dumpCommand("cmd window dump-visible-window-views", out); out.closeEntry(); - if (viewCapture != null) { + if (viewCaptureDataSupplier != null) { out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc")); - viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext()); + final ExportedData exportedData = viewCaptureDataSupplier.get(); + if (exportedData != null) exportedData.writeTo(out); out.closeEntry(); } } catch (Exception ignored) { diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java deleted file mode 100644 index e9a52f84df..0000000000 --- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.launcher3.util.rule; - -import android.app.Activity; - -import com.android.launcher3.Launcher; -import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; - -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.util.concurrent.Callable; - -/** - * Test rule to get the current Launcher activity. - */ -public class LauncherActivityRule extends SimpleActivityRule<Launcher> { - - public LauncherActivityRule() { - super(Launcher.class); - } - - @Override - public Statement apply(Statement base, Description description) { - - return new MyStatement(base) { - @Override - public void onActivityStarted(Activity activity) { - if (activity instanceof Launcher) { - ((Launcher) activity).getRotationHelper().forceAllowRotationForTesting(true); - } - } - }; - } - - public Callable<Boolean> itemExists(final ItemOperator op) { - return () -> { - Launcher launcher = getActivity(); - if (launcher == null) { - return false; - } - return launcher.getWorkspace().getFirstMatch(op) != null; - }; - } -}
\ No newline at end of file diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java deleted file mode 100644 index 2eedec30b6..0000000000 --- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.android.launcher3.util.rule; - -import android.app.Activity; -import android.app.Application; -import android.app.Application.ActivityLifecycleCallbacks; -import android.os.Bundle; - -import androidx.test.InstrumentationRegistry; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -/** - * Test rule to get the current activity. - */ -public class SimpleActivityRule<T extends Activity> implements TestRule { - - private final Class<T> mClass; - private T mActivity; - - public SimpleActivityRule(Class<T> clazz) { - mClass = clazz; - } - - @Override - public Statement apply(Statement base, Description description) { - return new MyStatement(base); - } - - public T getActivity() { - return mActivity; - } - - protected class MyStatement extends Statement implements ActivityLifecycleCallbacks { - - private final Statement mBase; - - public MyStatement(Statement base) { - mBase = base; - } - - @Override - public void evaluate() throws Throwable { - Application app = (Application) - InstrumentationRegistry.getTargetContext().getApplicationContext(); - app.registerActivityLifecycleCallbacks(this); - try { - mBase.evaluate(); - } finally { - app.unregisterActivityLifecycleCallbacks(this); - mActivity = null; - } - } - - @Override - public void onActivityCreated(Activity activity, Bundle bundle) { - if (activity != null && mClass.isInstance(activity)) { - mActivity = (T) activity; - } - } - - @Override - public void onActivityStarted(Activity activity) { - } - - @Override - public void onActivityResumed(Activity activity) { - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { - } - - @Override - public void onActivityDestroyed(Activity activity) { - if (activity == mActivity) { - mActivity = null; - } - } - } -}
\ No newline at end of file diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java index f33a50ae5a..38de07192a 100644 --- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java +++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java @@ -17,6 +17,8 @@ package com.android.launcher3.util.rule; import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assume.assumeTrue; + import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; @@ -69,12 +71,9 @@ public class TestStabilityRule implements TestRule { return new Statement() { @Override public void evaluate() throws Throwable { - if ((stability.flavors() & getRunFlavor()) != 0) { - Log.d(TAG, "Running " + description.getDisplayName()); - base.evaluate(); - } else { - Log.d(TAG, "Skipping " + description.getDisplayName()); - } + assumeTrue("Ignoring the test due to @Stability annotation", + (stability.flavors() & getRunFlavor()) != 0); + base.evaluate(); } }; } else { 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." + ) } } } |