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