1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.platform.test.rule; 18 19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 20 21 import android.os.ParcelFileDescriptor; 22 import android.os.Trace; 23 24 import androidx.annotation.NonNull; 25 import androidx.test.InstrumentationRegistry; 26 import androidx.test.uiautomator.UiDevice; 27 28 import org.junit.runner.Description; 29 30 import java.io.BufferedOutputStream; 31 import java.io.File; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.OutputStream; 35 import java.util.zip.ZipEntry; 36 import java.util.zip.ZipOutputStream; 37 38 /** Utilities for producing test artifacts. */ 39 public class ArtifactSaver { 40 private static final String TAG = ArtifactSaver.class.getSimpleName(); 41 42 // Presubmit tests have a time limit. We are not taking expensive bugreports from presubmits. 43 private static boolean sShouldTakeBugreport = !PresubmitRule.runningInPresubmit(); 44 artifactFile(String fileName)45 public static File artifactFile(String fileName) { 46 return new File( 47 InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir(), 48 fileName); 49 } 50 artifactFile(Description description, String prefix, String ext)51 static File artifactFile(Description description, String prefix, String ext) { 52 return artifactFile( 53 "TestScreenshot-" + prefix + "-" + getClassAndMethodName(description) + "." + ext); 54 } 55 getClassAndMethodName(Description description)56 private static String getClassAndMethodName(Description description) { 57 String suffix = description.getMethodName(); 58 if (suffix == null) { 59 // Can happen when the description is from a ClassRule 60 suffix = "EntireClassExecution"; 61 } 62 Class<?> testClass = description.getTestClass(); 63 64 // Can have null class if this is a synthetic suite 65 String className = testClass != null ? testClass.getSimpleName() : "SUITE"; 66 return className + "." + suffix; 67 } 68 onError(Description description, Throwable e)69 public static void onError(Description description, Throwable e) { 70 Trace.beginSection("ArtifactSaver.onError"); 71 final UiDevice device = getUiDevice(); 72 final File hierarchy = artifactFile(description, "Hierarchy", "zip"); 73 74 final File screenshot = takeDebugScreenshot(description, device, "OnFailure"); 75 76 // Dump accessibility hierarchy 77 try { 78 device.dumpWindowHierarchy(artifactFile(description, "AccessibilityHierarchy", "uix")); 79 } catch (Exception ex) { 80 android.util.Log.e(TAG, "Failed to save accessibility hierarchy", ex); 81 } 82 83 // Dump window hierarchy 84 try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) { 85 out.putNextEntry(new ZipEntry("bugreport.txt")); 86 dumpCommandAndOutput("dumpsys window windows", out); 87 dumpCommandAndOutput("dumpsys package", out); 88 out.closeEntry(); 89 90 out.putNextEntry(new ZipEntry("visible_windows.zip")); 91 dumpCommandOutput("cmd window dump-visible-window-views", out); 92 out.closeEntry(); 93 } catch (IOException ex) { 94 } 95 96 android.util.Log.e( 97 TAG, 98 "Failed test " 99 + description.getMethodName() 100 + ",\nscreenshot will be saved to " 101 + screenshot 102 + ",\nUI dump at: " 103 + hierarchy 104 + " (use go/web-hv to open the dump file)", 105 e); 106 107 // Dump bugreport 108 if (sShouldTakeBugreport && FailureWatcher.getSystemAnomalyMessage(device) != null) { 109 // Taking bugreport is expensive, we should do this only once. 110 sShouldTakeBugreport = false; 111 dumpCommandOutput("bugreportz -s", artifactFile(description, "Bugreport", "zip")); 112 } 113 114 dumpCommandOutput( 115 "dumpsys meminfo", 116 artifactFile("MemInfo-OnFailure-" + getClassAndMethodName(description) + ".txt")); 117 118 dumpCommandOutput( 119 "cmd statusbar flag | tail +11", // Flags info starts at line 11 120 artifactFile("Flags-OnFailure-" + getClassAndMethodName(description) + ".txt")); 121 122 Trace.endSection(); 123 } 124 getUiDevice()125 private static UiDevice getUiDevice() { 126 return UiDevice.getInstance(getInstrumentation()); 127 } 128 129 @NonNull takeDebugScreenshot(Description description, UiDevice device, String prefix)130 private static File takeDebugScreenshot(Description description, UiDevice device, 131 String prefix) { 132 final File screenshot = artifactFile(description, prefix, "png"); 133 device.takeScreenshot(screenshot); 134 return screenshot; 135 } 136 takeDebugScreenshot(Description description, String prefix)137 public static void takeDebugScreenshot(Description description, String prefix) { 138 File screenshotFile = takeDebugScreenshot(description, getUiDevice(), prefix); 139 android.util.Log.e( 140 TAG, 141 "Screenshot taken in test: " 142 + description.getMethodName() 143 + ",\nscreenshot will be saved to " 144 + screenshotFile); 145 } 146 dumpCommandAndOutput(String cmd, OutputStream out)147 private static void dumpCommandAndOutput(String cmd, OutputStream out) throws IOException { 148 out.write(("\n\n" + cmd + "\n").getBytes()); 149 dumpCommandOutput(cmd, out); 150 } 151 dumpCommandOutput(String cmd, File out)152 public static void dumpCommandOutput(String cmd, File out) { 153 try (BufferedOutputStream buffered = new BufferedOutputStream(new FileOutputStream(out))) { 154 dumpCommandOutput(cmd, buffered); 155 } catch (IOException ex) { 156 } 157 } 158 dumpCommandOutput(String cmd, OutputStream out)159 private static void dumpCommandOutput(String cmd, OutputStream out) throws IOException { 160 try (ParcelFileDescriptor.AutoCloseInputStream in = 161 new ParcelFileDescriptor.AutoCloseInputStream( 162 InstrumentationRegistry.getInstrumentation() 163 .getUiAutomation() 164 .executeShellCommand(cmd))) { 165 android.os.FileUtils.copy(in, out); 166 } 167 } 168 } 169