1 package com.android.launcher3.util.rule; 2 3 import static androidx.test.InstrumentationRegistry.getInstrumentation; 4 5 import android.content.Context; 6 import android.os.FileUtils; 7 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 8 import android.util.Log; 9 10 import androidx.test.uiautomator.UiDevice; 11 12 import com.android.launcher3.tapl.LauncherInstrumentation; 13 import com.android.launcher3.ui.AbstractLauncherUiTest; 14 15 import org.junit.rules.TestWatcher; 16 import org.junit.runner.Description; 17 import org.junit.runners.model.Statement; 18 19 import java.io.BufferedOutputStream; 20 import java.io.File; 21 import java.io.FileOutputStream; 22 import java.io.IOException; 23 import java.io.OutputStream; 24 import java.util.zip.ZipEntry; 25 import java.util.zip.ZipOutputStream; 26 27 public class FailureWatcher extends TestWatcher { 28 private static final String TAG = "FailureWatcher"; 29 final private UiDevice mDevice; 30 private final LauncherInstrumentation mLauncher; 31 FailureWatcher(UiDevice device, LauncherInstrumentation launcher)32 public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) { 33 mDevice = device; 34 mLauncher = launcher; 35 Log.d("b/196820244", "FailureWatcher.ctor", new Exception()); 36 } 37 38 @Override succeeded(Description description)39 protected void succeeded(Description description) { 40 super.succeeded(description); 41 AbstractLauncherUiTest.checkDetectedLeaks(mLauncher); 42 } 43 44 @Override apply(Statement base, Description description)45 public Statement apply(Statement base, Description description) { 46 return new Statement() { 47 @Override 48 public void evaluate() throws Throwable { 49 boolean success = false; 50 try { 51 Log.d("b/196820244", "Before evaluate"); 52 mDevice.executeShellCommand("cmd statusbar tracing start"); 53 FailureWatcher.super.apply(base, description).evaluate(); 54 Log.d("b/196820244", "After evaluate"); 55 success = true; 56 } finally { 57 // Save artifact for Launcher Winscope trace. 58 mDevice.executeShellCommand("cmd statusbar tracing stop"); 59 final Context nexusLauncherContext = 60 getInstrumentation().getTargetContext() 61 .createPackageContext("com.google.android.apps.nexuslauncher", 62 0); 63 final File launcherTrace = 64 new File(nexusLauncherContext.getFilesDir(), "launcher_trace.pb"); 65 if (success) { 66 mDevice.executeShellCommand("rm " + launcherTrace); 67 } else { 68 mDevice.executeShellCommand("mv " + launcherTrace + " " 69 + diagFile(description, "LauncherWinscope", "pb")); 70 } 71 72 // Detect touch events coming from physical screen. 73 if (mLauncher.hadNontestEvents()) { 74 throw new AssertionError( 75 "Launcher received events not sent by the test. This may mean " 76 + "that the touch screen of the lab device has sent false" 77 + " events. See the logcat for TaplEvents tag and look " 78 + "for events with deviceId != -1"); 79 } 80 } 81 } 82 }; 83 } 84 85 @Override 86 protected void failed(Throwable e, Description description) { 87 onError(mDevice, description, e); 88 } 89 90 static File diagFile(Description description, String prefix, String ext) { 91 return new File(getInstrumentation().getTargetContext().getFilesDir(), 92 prefix + "-" + description.getTestClass().getSimpleName() + "." 93 + description.getMethodName() + "." + ext); 94 } 95 96 public static void onError(UiDevice device, Description description, Throwable e) { 97 Log.d("b/196820244", "onError 1"); 98 if (device == null) return; 99 Log.d("b/196820244", "onError 2"); 100 final File sceenshot = diagFile(description, "TestScreenshot", "png"); 101 final File hierarchy = diagFile(description, "Hierarchy", "zip"); 102 103 // Dump window hierarchy 104 try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) { 105 out.putNextEntry(new ZipEntry("bugreport.txt")); 106 dumpStringCommand("dumpsys window windows", out); 107 dumpStringCommand("dumpsys package", out); 108 dumpStringCommand("dumpsys activity service TouchInteractionService", out); 109 out.closeEntry(); 110 111 out.putNextEntry(new ZipEntry("visible_windows.zip")); 112 dumpCommand("cmd window dump-visible-window-views", out); 113 out.closeEntry(); 114 } catch (IOException ex) { 115 } 116 117 Log.e(TAG, "Failed test " + description.getMethodName() 118 + ",\nscreenshot will be saved to " + sceenshot 119 + ",\nUI dump at: " + hierarchy 120 + " (use go/web-hv to open the dump file)", e); 121 device.takeScreenshot(sceenshot); 122 123 // Dump accessibility hierarchy 124 try { 125 device.dumpWindowHierarchy(diagFile(description, "AccessibilityHierarchy", "uix")); 126 } catch (IOException ex) { 127 Log.e(TAG, "Failed to save accessibility hierarchy", ex); 128 } 129 130 dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt")); 131 } 132 133 private static void dumpStringCommand(String cmd, OutputStream out) throws IOException { 134 out.write(("\n\n" + cmd + "\n").getBytes()); 135 dumpCommand(cmd, out); 136 } 137 138 private static void dumpCommand(String cmd, File out) { 139 try (BufferedOutputStream buffered = new BufferedOutputStream( 140 new FileOutputStream(out))) { 141 dumpCommand(cmd, buffered); 142 } catch (IOException ex) { 143 } 144 } 145 146 private static void dumpCommand(String cmd, OutputStream out) throws IOException { 147 try (AutoCloseInputStream in = new AutoCloseInputStream(getInstrumentation() 148 .getUiAutomation().executeShellCommand(cmd))) { 149 FileUtils.copy(in, out); 150 } 151 } 152 } 153