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 package com.android.server;
17 
18 import android.annotation.Nullable;
19 import android.util.Dumpable;
20 import android.util.Log;
21 
22 import org.junit.rules.TestRule;
23 import org.junit.runner.Description;
24 import org.junit.runners.model.Statement;
25 
26 import java.io.IOException;
27 import java.io.PrintWriter;
28 import java.io.StringWriter;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * {@code JUnit} rule that logs (using tag {@value #TAG} the contents of
34  * {@link Dumpable dumpables} in case of failure.
35  */
36 public final class DumpableDumperRule implements TestRule {
37 
38     private static final String TAG = DumpableDumperRule.class.getSimpleName();
39 
40     private static final String[] NO_ARGS = {};
41 
42     private final List<Dumpable> mDumpables = new ArrayList<>();
43 
44     private @Nullable String mTestName;
45 
46     /**
47      * Adds a {@link Dumpable} to be logged if the test case fails.
48      */
addDumpable(Dumpable dumpable)49     public void addDumpable(Dumpable dumpable) {
50         mDumpables.add(dumpable);
51     }
52 
53     /**
54      * Gets the name of the test being executed.
55      */
getTestName()56     public @Nullable String getTestName() {
57         return mTestName;
58     }
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                 mTestName = description.getDisplayName();
66                 try {
67                     base.evaluate();
68                 } catch (Throwable t) {
69                     dumpOnFailure(mTestName);
70                     mTestName = null;
71                     throw t;
72                 }
73             }
74         };
75     }
76 
77     /**
78      * Logs all dumpables.
79      */
80     public void dump(String reason) {
81         if (mDumpables.isEmpty()) {
82             return;
83         }
84         Log.w(TAG, "Dumping " + mDumpables.size() + " dumpable(s). Reason: " + reason);
85         mDumpables.forEach(d -> logDumpable(d));
86     }
87 
88     private void dumpOnFailure(String testName) throws IOException {
89         dump("failure of " + testName);
90     }
91 
92     private void logDumpable(Dumpable dumpable) {
93         try {
94             try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
95                 dumpable.dump(pw, NO_ARGS);
96                 String[] dump = sw.toString().split(System.lineSeparator());
97                 Log.w(TAG, "Dumping " + dumpable.getDumpableName() + " (" + dump.length
98                         + " lines):");
99                 for (String line : dump) {
100                     Log.w(TAG, line);
101                 }
102 
103             } catch (RuntimeException e) {
104                 Log.e(TAG, "RuntimeException dumping " + dumpable.getDumpableName(), e);
105             }
106         } catch (IOException e) {
107             Log.e(TAG, "IOException dumping " + dumpable.getDumpableName(), e);
108         }
109     }
110 }
111