1 package com.android.launcher3.util.rule; 2 3 import static androidx.test.InstrumentationRegistry.getInstrumentation; 4 5 import android.os.FileUtils; 6 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 7 import android.util.Log; 8 9 import androidx.annotation.NonNull; 10 import androidx.annotation.Nullable; 11 import androidx.test.uiautomator.UiDevice; 12 13 import com.android.app.viewcapture.data.ExportedData; 14 import com.android.launcher3.tapl.LauncherInstrumentation; 15 import com.android.launcher3.ui.AbstractLauncherUiTest; 16 17 import org.junit.rules.TestWatcher; 18 import org.junit.runner.Description; 19 import org.junit.runners.model.Statement; 20 21 import java.io.BufferedOutputStream; 22 import java.io.File; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.OutputStream; 26 import java.util.function.Supplier; 27 import java.util.zip.ZipEntry; 28 import java.util.zip.ZipOutputStream; 29 30 public class FailureWatcher extends TestWatcher { 31 private static final String TAG = "FailureWatcher"; 32 private static boolean sSavedBugreport = false; 33 private static Description sDescriptionForLastSavedArtifacts; 34 35 private final LauncherInstrumentation mLauncher; 36 @NonNull 37 private final Supplier<ExportedData> mViewCaptureDataSupplier; 38 FailureWatcher(LauncherInstrumentation launcher, @NonNull Supplier<ExportedData> viewCaptureDataSupplier)39 public FailureWatcher(LauncherInstrumentation launcher, 40 @NonNull Supplier<ExportedData> viewCaptureDataSupplier) { 41 mLauncher = launcher; 42 mViewCaptureDataSupplier = viewCaptureDataSupplier; 43 } 44 45 @Override starting(Description description)46 protected void starting(Description description) { 47 mLauncher.setOnFailure(() -> onError(mLauncher, description, mViewCaptureDataSupplier)); 48 super.starting(description); 49 } 50 51 @Override finished(Description description)52 protected void finished(Description description) { 53 super.finished(description); 54 mLauncher.setOnFailure(null); 55 } 56 57 @Override succeeded(Description description)58 protected void succeeded(Description description) { 59 super.succeeded(description); 60 AbstractLauncherUiTest.checkDetectedLeaks(mLauncher); 61 } 62 63 @Override apply(Statement base, Description description)64 public Statement apply(Statement base, Description description) { 65 return new Statement() { 66 @Override 67 public void evaluate() throws Throwable { 68 try { 69 FailureWatcher.super.apply(base, description).evaluate(); 70 } finally { 71 // Detect touch events coming from physical screen. 72 if (mLauncher.hadNontestEvents()) { 73 throw new AssertionError( 74 "Launcher received events not sent by the test. This may mean " 75 + "that the touch screen of the lab device has sent false" 76 + " events. See the logcat for " 77 + "TaplEvents|LauncherEvents|TaplTarget tag and look for " 78 + "events with deviceId != -1"); 79 } 80 } 81 } 82 }; 83 } 84 85 @Override 86 protected void failed(Throwable e, Description description) { 87 onError(mLauncher, description, mViewCaptureDataSupplier); 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 /** Action executed when an error condition is expected. Saves artifacts. */ 97 public static void onError(LauncherInstrumentation launcher, Description description) { 98 onError(launcher, description, null); 99 } 100 101 private static void onError(LauncherInstrumentation launcher, Description description, 102 @Nullable Supplier<ExportedData> viewCaptureDataSupplier) { 103 if (description.equals(sDescriptionForLastSavedArtifacts)) { 104 // This test has already saved its artifacts. 105 return; 106 } 107 sDescriptionForLastSavedArtifacts = description; 108 109 final File sceenshot = diagFile(description, "TestScreenshot", "png"); 110 final File hierarchy = diagFile(description, "Hierarchy", "zip"); 111 112 // Dump window hierarchy 113 try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) { 114 out.putNextEntry(new ZipEntry("bugreport.txt")); 115 dumpStringCommand("dumpsys window windows", out); 116 dumpStringCommand("dumpsys package", out); 117 dumpStringCommand("dumpsys activity service TouchInteractionService", out); 118 out.closeEntry(); 119 120 out.putNextEntry(new ZipEntry("visible_windows.zip")); 121 dumpCommand("cmd window dump-visible-window-views", out); 122 out.closeEntry(); 123 124 if (viewCaptureDataSupplier != null) { 125 out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc")); 126 final ExportedData exportedData = viewCaptureDataSupplier.get(); 127 if (exportedData != null) exportedData.writeTo(out); 128 out.closeEntry(); 129 } 130 } catch (Exception ignored) { 131 } 132 133 Log.e(TAG, "Failed test " + description.getMethodName() 134 + ",\nscreenshot will be saved to " + sceenshot 135 + ",\nUI dump at: " + hierarchy 136 + " (use go/web-hv to open the dump file)"); 137 final UiDevice device = launcher.getDevice(); 138 device.takeScreenshot(sceenshot); 139 140 // Dump accessibility hierarchy 141 try { 142 device.dumpWindowHierarchy(diagFile(description, "AccessibilityHierarchy", "uix")); 143 } catch (IOException ex) { 144 Log.e(TAG, "Failed to save accessibility hierarchy", ex); 145 } 146 147 // Dump bugreport 148 if (!sSavedBugreport) { 149 dumpCommand("bugreportz -s", diagFile(description, "Bugreport", "zip")); 150 // Not saving bugreport for each failure for time and space economy. 151 sSavedBugreport = true; 152 } 153 } 154 155 private static void dumpStringCommand(String cmd, OutputStream out) throws IOException { 156 out.write(("\n\n" + cmd + "\n").getBytes()); 157 dumpCommand(cmd, out); 158 } 159 160 private static void dumpCommand(String cmd, File out) { 161 try (BufferedOutputStream buffered = new BufferedOutputStream( 162 new FileOutputStream(out))) { 163 dumpCommand(cmd, buffered); 164 } catch (IOException ex) { 165 } 166 } 167 168 private static void dumpCommand(String cmd, OutputStream out) throws IOException { 169 try (AutoCloseInputStream in = new AutoCloseInputStream(getInstrumentation() 170 .getUiAutomation().executeShellCommand(cmd))) { 171 FileUtils.copy(in, out); 172 } 173 } 174 } 175