1 /* 2 * Copyright (C) 2019 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.server.wm; 18 19 import android.graphics.Bitmap; 20 import android.util.Log; 21 22 import com.android.compatibility.common.util.BitmapUtils; 23 24 import org.junit.rules.TestRule; 25 import org.junit.runner.Description; 26 import org.junit.runners.model.Statement; 27 28 import java.io.File; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** 35 * A {@code TestRule} that allows dumping data on test failure. 36 * 37 * <p>Note: when using other {@code TestRule}s, make sure to use a {@code RuleChain} to ensure it 38 * is applied outside of other rules that can fail a test (otherwise this rule may not know that the 39 * test failed). 40 * 41 * <p>To capture the output of this rule, add the following to AndroidTest.xml: 42 * <pre> 43 * <!-- Collect output of DumpOnFailure. --> 44 * <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> 45 * <option name="directory-keys" value="/sdcard/DumpOnFailure" /> 46 * <option name="collect-on-run-ended-only" value="true" /> 47 * </metrics_collector> 48 * </pre> 49 * <p>And disable external storage isolation: 50 * <pre> 51 * <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> 52 * <application ... android:requestLegacyExternalStorage="true" ... > 53 * </pre> 54 */ 55 public class DumpOnFailure implements TestRule { 56 57 private static final String TAG = "DumpOnFailure"; 58 59 private final Map<String, Bitmap> mDumpOnFailureBitmaps = new HashMap<>(); 60 61 @Override apply(Statement base, Description description)62 public Statement apply(Statement base, Description description) { 63 return new Statement() { 64 @Override 65 public void evaluate() throws Throwable { 66 onTestSetup(description); 67 try { 68 base.evaluate(); 69 } catch (Throwable t) { 70 onTestFailure(description, t); 71 throw t; 72 } finally { 73 onTestTeardown(description); 74 } 75 } 76 }; 77 } 78 79 private void onTestSetup(Description description) { 80 cleanDir(getDumpRoot(description).toFile()); 81 mDumpOnFailureBitmaps.clear(); 82 } 83 84 private void onTestTeardown(Description description) { 85 mDumpOnFailureBitmaps.clear(); 86 } 87 88 private void onTestFailure(Description description, Throwable t) { 89 Path root = getDumpRoot(description); 90 File rootFile = root.toFile(); 91 if (!rootFile.exists() && !rootFile.mkdirs()) { 92 throw new RuntimeException("Unable to create " + root); 93 } 94 95 for (Map.Entry<String, Bitmap> entry : mDumpOnFailureBitmaps.entrySet()) { 96 String fileName = getFilename(description, entry.getKey(), "png"); 97 Log.i(TAG, "Dumping " + root + "/" + fileName); 98 BitmapUtils.saveBitmap(entry.getValue(), root.toString(), fileName); 99 } 100 } 101 102 private String getFilename(Description description, String name, String extension) { 103 return description.getTestClass().getSimpleName() + "_" + description.getMethodName() 104 + "__" + name + "." + extension; 105 } 106 107 private Path getDumpRoot(Description description) { 108 return Paths.get("/sdcard/DumpOnFailure/", description.getClassName() 109 + "_" + description.getMethodName()); 110 } 111 112 private void cleanDir(File dir) { 113 final File[] files = dir.listFiles(); 114 if (files == null) { 115 return; 116 } 117 for (File file : files) { 118 if (!file.isDirectory()) { 119 if (!file.delete()) { 120 throw new RuntimeException("Unable to delete " + file); 121 } 122 } 123 } 124 } 125 126 /** 127 * Dumps the Bitmap if the test fails. 128 */ 129 public void dumpOnFailure(String name, Bitmap bitmap) { 130 if (mDumpOnFailureBitmaps.containsKey(name)) { 131 int i = 1; 132 while (mDumpOnFailureBitmaps.containsKey(name + "_" + i)) { 133 ++i; 134 } 135 name += "_" + i; 136 } 137 mDumpOnFailureBitmaps.put(name, bitmap); 138 } 139 } 140