• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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