Merge "Revert "Keep ViewCaptureRule logic self-contained."" into udc-dev am: 57eaf86f8d
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/23454664 Change-Id: I08180c952e4aef50845e5dc113c88c70b3629648 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -116,11 +116,12 @@ public class FallbackRecentsTest {
|
|||||||
Utilities.enableRunningInTestHarnessForTests();
|
Utilities.enableRunningInTestHarnessForTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
|
||||||
mOrderSensitiveRules = RuleChain
|
mOrderSensitiveRules = RuleChain
|
||||||
.outerRule(new SamplerRule())
|
.outerRule(new SamplerRule())
|
||||||
.around(new NavigationModeSwitchRule(mLauncher))
|
.around(new NavigationModeSwitchRule(mLauncher))
|
||||||
.around(new ViewCaptureRule())
|
.around(viewCaptureRule)
|
||||||
.around(new FailureWatcher(mDevice, mLauncher));
|
.around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
|
||||||
|
|
||||||
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
|
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
|
||||||
getHomeIntentInPackage(context),
|
getHomeIntentInPackage(context),
|
||||||
|
|||||||
@@ -216,10 +216,11 @@ public abstract class AbstractLauncherUiTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected TestRule getRulesInsideActivityMonitor() {
|
protected TestRule getRulesInsideActivityMonitor() {
|
||||||
|
final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
|
||||||
final RuleChain inner = RuleChain
|
final RuleChain inner = RuleChain
|
||||||
.outerRule(new PortraitLandscapeRunner(this))
|
.outerRule(new PortraitLandscapeRunner(this))
|
||||||
.around(new ViewCaptureRule())
|
.around(viewCaptureRule)
|
||||||
.around(new FailureWatcher(mDevice, mLauncher));
|
.around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
|
||||||
|
|
||||||
return TestHelpers.isInLauncherProcess()
|
return TestHelpers.isInLauncherProcess()
|
||||||
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
|
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import android.os.FileUtils;
|
|||||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
|
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.uiautomator.UiDevice;
|
import androidx.test.uiautomator.UiDevice;
|
||||||
|
|
||||||
|
import com.android.app.viewcapture.ViewCapture;
|
||||||
import com.android.launcher3.tapl.LauncherInstrumentation;
|
import com.android.launcher3.tapl.LauncherInstrumentation;
|
||||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||||
|
|
||||||
@@ -28,10 +32,14 @@ public class FailureWatcher extends TestWatcher {
|
|||||||
private static boolean sSavedBugreport = false;
|
private static boolean sSavedBugreport = false;
|
||||||
final private UiDevice mDevice;
|
final private UiDevice mDevice;
|
||||||
private final LauncherInstrumentation mLauncher;
|
private final LauncherInstrumentation mLauncher;
|
||||||
|
@NonNull
|
||||||
|
private final ViewCapture mViewCapture;
|
||||||
|
|
||||||
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
|
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher,
|
||||||
|
@NonNull ViewCapture viewCapture) {
|
||||||
mDevice = device;
|
mDevice = device;
|
||||||
mLauncher = launcher;
|
mLauncher = launcher;
|
||||||
|
mViewCapture = viewCapture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,7 +71,7 @@ public class FailureWatcher extends TestWatcher {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failed(Throwable e, Description description) {
|
protected void failed(Throwable e, Description description) {
|
||||||
onError(mLauncher, description, e);
|
onError(mLauncher, description, e, mViewCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
static File diagFile(Description description, String prefix, String ext) {
|
static File diagFile(Description description, String prefix, String ext) {
|
||||||
@@ -74,6 +82,12 @@ public class FailureWatcher extends TestWatcher {
|
|||||||
|
|
||||||
public static void onError(LauncherInstrumentation launcher, Description description,
|
public static void onError(LauncherInstrumentation launcher, Description description,
|
||||||
Throwable e) {
|
Throwable e) {
|
||||||
|
onError(launcher, description, e, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onError(LauncherInstrumentation launcher, Description description,
|
||||||
|
Throwable e, @Nullable ViewCapture viewCapture) {
|
||||||
|
|
||||||
final File sceenshot = diagFile(description, "TestScreenshot", "png");
|
final File sceenshot = diagFile(description, "TestScreenshot", "png");
|
||||||
final File hierarchy = diagFile(description, "Hierarchy", "zip");
|
final File hierarchy = diagFile(description, "Hierarchy", "zip");
|
||||||
|
|
||||||
@@ -88,6 +102,12 @@ public class FailureWatcher extends TestWatcher {
|
|||||||
out.putNextEntry(new ZipEntry("visible_windows.zip"));
|
out.putNextEntry(new ZipEntry("visible_windows.zip"));
|
||||||
dumpCommand("cmd window dump-visible-window-views", out);
|
dumpCommand("cmd window dump-visible-window-views", out);
|
||||||
out.closeEntry();
|
out.closeEntry();
|
||||||
|
|
||||||
|
if (viewCapture != null) {
|
||||||
|
out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc"));
|
||||||
|
viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext());
|
||||||
|
out.closeEntry();
|
||||||
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,101 +19,62 @@ import android.app.Activity
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.media.permission.SafeCloseable
|
import android.media.permission.SafeCloseable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.AnyThread
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.android.app.viewcapture.SimpleViewCapture
|
import com.android.app.viewcapture.SimpleViewCapture
|
||||||
import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR
|
import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR
|
||||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
|
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
|
||||||
import java.io.File
|
import org.junit.rules.TestRule
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
import org.junit.rules.TestWatcher
|
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
private const val TAG = "ViewCaptureRule"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture
|
* This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture
|
||||||
* instance that other test rules use to dump the timelapse hierarchy upon an error during a test.
|
* instance that other test rules use to dump the timelapse hierarchy upon an error during a test.
|
||||||
*
|
*
|
||||||
* This rule will not work in OOP tests that don't have access to the activity under test.
|
* This rule will not work in OOP tests that don't have access to the activity under test.
|
||||||
*/
|
*/
|
||||||
class ViewCaptureRule : TestWatcher() {
|
class ViewCaptureRule : TestRule {
|
||||||
private val viewCapture = SimpleViewCapture("test-view-capture")
|
val viewCapture = SimpleViewCapture("test-view-capture")
|
||||||
private val windowListenerCloseables = mutableListOf<SafeCloseable>()
|
|
||||||
|
|
||||||
override fun apply(base: Statement, description: Description): Statement {
|
override fun apply(base: Statement, description: Description): Statement {
|
||||||
val testWatcherStatement = super.apply(base, description)
|
|
||||||
|
|
||||||
return object : Statement() {
|
return object : Statement() {
|
||||||
override fun evaluate() {
|
override fun evaluate() {
|
||||||
val lifecycleCallbacks = createLifecycleCallbacks(description)
|
val windowListenerCloseables = mutableListOf<SafeCloseable>()
|
||||||
with(ApplicationProvider.getApplicationContext<Application>()) {
|
|
||||||
registerActivityLifecycleCallbacks(lifecycleCallbacks)
|
val lifecycleCallbacks =
|
||||||
try {
|
object : ActivityLifecycleCallbacksAdapter {
|
||||||
testWatcherStatement.evaluate()
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
|
||||||
} finally {
|
super.onActivityCreated(activity, bundle)
|
||||||
unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
|
windowListenerCloseables.add(
|
||||||
|
viewCapture.startCapture(
|
||||||
|
activity.window.decorView,
|
||||||
|
"${description.testClass?.simpleName}.${description.methodName}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
super.onActivityDestroyed(activity)
|
||||||
|
viewCapture.stopCapture(activity.window.decorView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val application = ApplicationProvider.getApplicationContext<Application>()
|
||||||
|
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
|
||||||
|
|
||||||
|
try {
|
||||||
|
base.evaluate()
|
||||||
|
} finally {
|
||||||
|
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This is on the main thread to avoid a race condition where the onDrawListener
|
||||||
|
// is removed while onDraw is running, resulting in an IllegalStateException.
|
||||||
|
MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLifecycleCallbacks(description: Description) =
|
|
||||||
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}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityDestroyed(activity: Activity) {
|
|
||||||
super.onActivityDestroyed(activity)
|
|
||||||
viewCapture.stopCapture(activity.window.decorView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun succeeded(description: Description) = cleanup()
|
|
||||||
|
|
||||||
/** If the test fails, this function will output the ViewCapture information. */
|
|
||||||
override fun failed(e: Throwable, description: Description) {
|
|
||||||
super.failed(e, description)
|
|
||||||
|
|
||||||
val testName = "${description.testClass.simpleName}.${description.methodName}"
|
|
||||||
val application: Application = ApplicationProvider.getApplicationContext()
|
|
||||||
val zip = File(application.filesDir, "ViewCapture-$testName.zip")
|
|
||||||
|
|
||||||
ZipOutputStream(FileOutputStream(zip)).use {
|
|
||||||
it.putNextEntry(ZipEntry("FS/data/misc/wmtrace/failed_test.vc"))
|
|
||||||
viewCapture.dumpTo(it, ApplicationProvider.getApplicationContext())
|
|
||||||
it.closeEntry()
|
|
||||||
}
|
|
||||||
cleanup()
|
|
||||||
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"Failed $testName due to ${e::class.java.simpleName}.\n" +
|
|
||||||
"\tUse go/web-hv to open dump file: \n\t\t${zip.absolutePath}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean up ViewCapture references can't happen in onActivityDestroyed otherwise view
|
|
||||||
* hierarchies would be erased before they could be outputted.
|
|
||||||
*
|
|
||||||
* This is on the main thread to avoid a race condition where the onDrawListener is removed
|
|
||||||
* while onDraw is running, resulting in an IllegalStateException.
|
|
||||||
*/
|
|
||||||
@AnyThread
|
|
||||||
private fun cleanup() {
|
|
||||||
MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user