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 * <application ... android:requestLegacyExternalStorage="true" ... > 52 * </pre> 53 */ 54 public class DumpOnFailure implements TestRule { 55 56 private static final String TAG = "DumpOnFailure"; 57 58 private final Map<String, Bitmap> mDumpOnFailureBitmaps = new HashMap<>(); 59 60 @Override apply(Statement base, Description description)61 public Statement apply(Statement base, Description description) { 62 return new Statement() { 63 @Override 64 public void evaluate() throws Throwable { 65 onTestSetup(description); 66 try { 67 base.evaluate(); 68 } catch (Throwable t) { 69 onTestFailure(description, t); 70 throw t; 71 } finally { 72 onTestTeardown(description); 73 } 74 } 75 }; 76 } 77 78 private void onTestSetup(Description description) { 79 cleanDir(getDumpRoot(description).toFile()); 80 mDumpOnFailureBitmaps.clear(); 81 } 82 83 private void onTestTeardown(Description description) { 84 mDumpOnFailureBitmaps.clear(); 85 } 86 87 private void onTestFailure(Description description, Throwable t) { 88 Path root = getDumpRoot(description); 89 File rootFile = root.toFile(); 90 if (!rootFile.exists() && !rootFile.mkdirs()) { 91 throw new RuntimeException("Unable to create " + root); 92 } 93 94 for (Map.Entry<String, Bitmap> entry : mDumpOnFailureBitmaps.entrySet()) { 95 String fileName = getFilename(description, entry.getKey(), "png"); 96 Log.i(TAG, "Dumping " + root + "/" + fileName); 97 BitmapUtils.saveBitmap(entry.getValue(), root.toString(), fileName); 98 } 99 } 100 101 private String getFilename(Description description, String name, String extension) { 102 return description.getTestClass().getSimpleName() + "_" + description.getMethodName() 103 + "__" + name + "." + extension; 104 } 105 106 private Path getDumpRoot(Description description) { 107 return Paths.get("/sdcard/DumpOnFailure/", description.getClassName() 108 + "_" + description.getMethodName()); 109 } 110 111 private void cleanDir(File dir) { 112 final File[] files = dir.listFiles(); 113 if (files == null) { 114 return; 115 } 116 for (File file : files) { 117 if (!file.isDirectory()) { 118 if (!file.delete()) { 119 throw new RuntimeException("Unable to delete " + file); 120 } 121 } 122 } 123 } 124 125 /** 126 * Dumps the Bitmap if the test fails. 127 */ 128 public void dumpOnFailure(String name, Bitmap bitmap) { 129 if (mDumpOnFailureBitmaps.containsKey(name)) { 130 int i = 1; 131 while (mDumpOnFailureBitmaps.containsKey(name + "_" + i)) { 132 ++i; 133 } 134 name += "_" + i; 135 } 136 mDumpOnFailureBitmaps.put(name, bitmap); 137 } 138 } 139