blob: 4c41d7ec4f25c073b246abcbf28dc1580b54f281 [file] [log] [blame]
package com.android.launcher3.util.rule;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.content.Context;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.util.Log;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
final private UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
mDevice = device;
mLauncher = launcher;
Log.d("b/196820244", "FailureWatcher.ctor", new Exception());
}
@Override
protected void succeeded(Description description) {
super.succeeded(description);
AbstractLauncherUiTest.checkDetectedLeaks(mLauncher);
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
boolean success = false;
try {
Log.d("b/196820244", "Before evaluate");
mDevice.executeShellCommand("cmd statusbar tracing start");
FailureWatcher.super.apply(base, description).evaluate();
Log.d("b/196820244", "After evaluate");
success = true;
} finally {
// Save artifact for Launcher Winscope trace.
mDevice.executeShellCommand("cmd statusbar tracing stop");
final Context nexusLauncherContext =
getInstrumentation().getTargetContext()
.createPackageContext("com.google.android.apps.nexuslauncher",
0);
final File launcherTrace =
new File(nexusLauncherContext.getFilesDir(), "launcher_trace.pb");
if (success) {
mDevice.executeShellCommand("rm " + launcherTrace);
} else {
mDevice.executeShellCommand("mv " + launcherTrace + " "
+ diagFile(description, "LauncherWinscope", "pb"));
}
// Detect touch events coming from physical screen.
if (mLauncher.hadNontestEvents()) {
throw new AssertionError(
"Launcher received events not sent by the test. This may mean "
+ "that the touch screen of the lab device has sent false"
+ " events. See the logcat for TaplEvents tag and look "
+ "for events with deviceId != -1");
}
}
}
};
}
@Override
protected void failed(Throwable e, Description description) {
onError(mLauncher, description, e);
}
static File diagFile(Description description, String prefix, String ext) {
return new File(getInstrumentation().getTargetContext().getFilesDir(),
prefix + "-" + description.getTestClass().getSimpleName() + "."
+ description.getMethodName() + "." + ext);
}
public static void onError(LauncherInstrumentation launcher, Description description,
Throwable e) {
final UiDevice device = launcher.getDevice();
Log.d("b/196820244", "onError 1");
if (device == null) return;
Log.d("b/196820244", "onError 2");
final File sceenshot = diagFile(description, "TestScreenshot", "png");
final File hierarchy = diagFile(description, "Hierarchy", "zip");
// Dump window hierarchy
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) {
out.putNextEntry(new ZipEntry("bugreport.txt"));
dumpStringCommand("dumpsys window windows", out);
dumpStringCommand("dumpsys package", out);
dumpStringCommand("dumpsys activity service TouchInteractionService", out);
out.closeEntry();
out.putNextEntry(new ZipEntry("visible_windows.zip"));
dumpCommand("cmd window dump-visible-window-views", out);
out.closeEntry();
} catch (IOException ex) {
}
Log.e(TAG, "Failed test " + description.getMethodName()
+ ",\nscreenshot will be saved to " + sceenshot
+ ",\nUI dump at: " + hierarchy
+ " (use go/web-hv to open the dump file)", e);
device.takeScreenshot(sceenshot);
// Dump accessibility hierarchy
try {
device.dumpWindowHierarchy(diagFile(description, "AccessibilityHierarchy", "uix"));
} catch (IOException ex) {
Log.e(TAG, "Failed to save accessibility hierarchy", ex);
}
dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt"));
// Dump bugreport
final String systemAnomalyMessage = launcher.getSystemAnomalyMessage(false, false);
if (systemAnomalyMessage != null) {
Log.d(TAG, "Saving bugreport, system anomaly message: " + systemAnomalyMessage, e);
dumpCommand("bugreportz -s", diagFile(description, "Bugreport", "zip"));
}
}
private static void dumpStringCommand(String cmd, OutputStream out) throws IOException {
out.write(("\n\n" + cmd + "\n").getBytes());
dumpCommand(cmd, out);
}
private static void dumpCommand(String cmd, File out) {
try (BufferedOutputStream buffered = new BufferedOutputStream(
new FileOutputStream(out))) {
dumpCommand(cmd, buffered);
} catch (IOException ex) {
}
}
private static void dumpCommand(String cmd, OutputStream out) throws IOException {
try (AutoCloseInputStream in = new AutoCloseInputStream(getInstrumentation()
.getUiAutomation().executeShellCommand(cmd))) {
FileUtils.copy(in, out);
}
}
}