Home | History | Annotate | Download | only in suite
      1 /*
      2  * Copyright (C) 2016 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.tradefed.testtype.suite;
     17 
     18 import com.android.tradefed.device.DeviceNotAvailableException;
     19 import com.android.tradefed.device.ITestDevice;
     20 import com.android.tradefed.log.ITestLogger;
     21 import com.android.tradefed.log.LogUtil.CLog;
     22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
     23 import com.android.tradefed.result.ITestInvocationListener;
     24 import com.android.tradefed.result.InputStreamSource;
     25 import com.android.tradefed.result.LogDataType;
     26 import com.android.tradefed.result.TestDescription;
     27 import com.android.tradefed.util.IRunUtil;
     28 import com.android.tradefed.util.RunUtil;
     29 
     30 import com.google.common.annotations.VisibleForTesting;
     31 
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.List;
     35 import java.util.Map;
     36 
     37 /**
     38  * Listener used to take action such as screenshot, bugreport, logcat collection upon a test failure
     39  * when requested.
     40  */
     41 public class TestFailureListener implements ITestInvocationListener {
     42 
     43     private static final int DEFAULT_MAX_LOGCAT_BYTES = 500 * 1024; // 500K
     44     /* Arbitrary upper limit for mMaxLogcatBytes to avoid un-reasonably high limit */
     45     private static final int LOGCAT_BYTE_LIMIT = 20 * 1024 * 1024; // 20 MB
     46     private static final String LOGCAT_ON_FAILURE_SIZE_OPTION = "logcat-on-failure-size";
     47     private static final long LOGCAT_CAPTURE_TIMEOUT = 2 * 60 * 1000;
     48 
     49     private List<ITestDevice> mListDevice;
     50     private ITestLogger mLogger;
     51     // Settings for the whole invocation
     52     private boolean mBugReportOnFailure;
     53     private boolean mLogcatOnFailure;
     54     private boolean mScreenshotOnFailure;
     55     private boolean mRebootOnFailure;
     56 
     57     // module specific values
     58     private boolean mModuleBugReportOnFailure = true;
     59     private boolean mModuleLogcatOnFailure = true;
     60     private boolean mModuleScreenshotOnFailure = true;
     61 
     62     private int mMaxLogcatBytes;
     63     private Map<TestDescription, Long> mTrackStartTime = new HashMap<>();
     64     private List<Thread> mLogcatThreads = new ArrayList<>();
     65 
     66     public TestFailureListener(
     67             List<ITestDevice> devices,
     68             boolean bugReportOnFailure,
     69             boolean logcatOnFailure,
     70             boolean screenshotOnFailure,
     71             boolean rebootOnFailure,
     72             int maxLogcatBytes) {
     73         mListDevice = devices;
     74         mBugReportOnFailure = bugReportOnFailure;
     75         mLogcatOnFailure = logcatOnFailure;
     76         mScreenshotOnFailure = screenshotOnFailure;
     77         mRebootOnFailure = rebootOnFailure;
     78         if (maxLogcatBytes < 0 ) {
     79             CLog.w("FailureListener could not set %s to '%d', using default value %d",
     80                     LOGCAT_ON_FAILURE_SIZE_OPTION, maxLogcatBytes,
     81                     DEFAULT_MAX_LOGCAT_BYTES);
     82             mMaxLogcatBytes = DEFAULT_MAX_LOGCAT_BYTES;
     83         } else if (maxLogcatBytes > LOGCAT_BYTE_LIMIT) {
     84             CLog.w("Value %d for %s exceeds limit %d, using limit value", maxLogcatBytes,
     85                     LOGCAT_ON_FAILURE_SIZE_OPTION, LOGCAT_BYTE_LIMIT);
     86             mMaxLogcatBytes = LOGCAT_BYTE_LIMIT;
     87         } else {
     88             mMaxLogcatBytes = maxLogcatBytes;
     89         }
     90     }
     91 
     92     /** We override testStarted in order to track the start time. */
     93     @Override
     94     public void testStarted(TestDescription test) {
     95         if (mLogcatOnFailure) {
     96             try {
     97                 mTrackStartTime.put(test, mListDevice.get(0).getDeviceDate());
     98             } catch (DeviceNotAvailableException e) {
     99                 CLog.e(e);
    100                 // we fall back to logcat dump on null.
    101                 mTrackStartTime.put(test, null);
    102             }
    103         }
    104     }
    105 
    106     /** Make sure we clean the map when test end to avoid too much overhead. */
    107     @Override
    108     public void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
    109         if (mLogcatOnFailure) {
    110             mTrackStartTime.remove(test);
    111         }
    112     }
    113 
    114     /** {@inheritDoc} */
    115     @Override
    116     public void testFailed(TestDescription test, String trace) {
    117         CLog.i("FailureListener.testFailed %s %b %b %b", test.toString(), mBugReportOnFailure,
    118                 mLogcatOnFailure, mScreenshotOnFailure);
    119         for (ITestDevice device : mListDevice) {
    120             captureFailure(device, test);
    121         }
    122     }
    123 
    124     /** Capture the appropriate logs for one device for one test failure. */
    125     private void captureFailure(ITestDevice device, TestDescription test) {
    126         String serial = device.getSerialNumber();
    127         if (mScreenshotOnFailure && mModuleScreenshotOnFailure) {
    128             try {
    129                 try (InputStreamSource screenSource = device.getScreenshot()) {
    130                     testLogForward(
    131                             String.format("%s-%s-screenshot", test.toString(), serial),
    132                             LogDataType.PNG,
    133                             screenSource);
    134                 }
    135             } catch (DeviceNotAvailableException e) {
    136                 CLog.e(e);
    137                 CLog.e("Device %s became unavailable while capturing screenshot", serial);
    138             }
    139         }
    140         if (mBugReportOnFailure && mModuleBugReportOnFailure) {
    141             if (!device.logBugreport(
    142                     String.format("%s-%s-bugreport", test.toString(), serial), mLogger)) {
    143                 CLog.e("Failed to capture bugreport for %s failure on %s.", test, serial);
    144             }
    145         }
    146         if (mLogcatOnFailure && mModuleLogcatOnFailure) {
    147             Runnable captureLogcat =
    148                     new Runnable() {
    149                         @Override
    150                         public void run() {
    151                             Long startTime = mTrackStartTime.remove(test);
    152                             if (startTime != null) {
    153                                 try (InputStreamSource logSource =
    154                                         device.getLogcatSince(startTime)) {
    155                                     testLogForward(
    156                                             String.format("%s-%s-logcat", test.toString(), serial),
    157                                             LogDataType.LOGCAT,
    158                                             logSource);
    159                                 }
    160                             } else {
    161                                 // sleep 2s to ensure test failure stack trace makes it into the
    162                                 // logcat capture
    163                                 getRunUtil().sleep(2 * 1000);
    164                                 try (InputStreamSource logSource =
    165                                         device.getLogcat(mMaxLogcatBytes)) {
    166                                     testLogForward(
    167                                             String.format("%s-%s-logcat", test.toString(), serial),
    168                                             LogDataType.LOGCAT,
    169                                             logSource);
    170                                 }
    171                             }
    172                         }
    173                     };
    174             if (mRebootOnFailure) {
    175                 captureLogcat.run();
    176             } else {
    177                 // If no reboot will be done afterward capture asynchronously the logcat.
    178                 Thread captureThread =
    179                         new Thread(captureLogcat, String.format("Capture failure logcat %s", test));
    180                 captureThread.setDaemon(true);
    181                 mLogcatThreads.add(captureThread);
    182                 captureThread.start();
    183             }
    184         }
    185         if (mRebootOnFailure) {
    186             try {
    187                 // Rebooting on all failures can hide legitimate issues and platform instabilities,
    188                 // therefore only allowed on "user-debug" and "eng" builds.
    189                 if ("user".equals(device.getProperty("ro.build.type"))) {
    190                     CLog.e("Reboot-on-failure should only be used during development," +
    191                             " this is a\" user\" build device");
    192                 } else {
    193                     device.reboot();
    194                 }
    195             } catch (DeviceNotAvailableException e) {
    196                 CLog.e(e);
    197                 CLog.e("Device %s became unavailable while rebooting", serial);
    198             }
    199         }
    200     }
    201 
    202     /** Join on all the logcat capturing threads to ensure they terminate. */
    203     public void join() {
    204         // Reset the module config to use the invocation settings by default.
    205         mModuleBugReportOnFailure = true;
    206         mModuleLogcatOnFailure = true;
    207         mModuleScreenshotOnFailure = true;
    208         synchronized (mLogcatThreads) {
    209             for (Thread t : mLogcatThreads) {
    210                 if (!t.isAlive()) {
    211                     continue;
    212                 }
    213                 try {
    214                     t.join(LOGCAT_CAPTURE_TIMEOUT);
    215                 } catch (InterruptedException e) {
    216                     CLog.e(e);
    217                 }
    218             }
    219             mLogcatThreads.clear();
    220         }
    221     }
    222 
    223     /**
    224      * Forward the log to the logger, do not do it from whitin the #testLog callback as if
    225      * TestFailureListener is part of the chain, it will results in an infinite loop.
    226      */
    227     public void testLogForward(
    228             String dataName, LogDataType dataType, InputStreamSource dataStream) {
    229         mLogger.testLog(dataName, dataType, dataStream);
    230     }
    231 
    232     @Override
    233     public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
    234         // Explicitly do nothing on testLog
    235     }
    236 
    237     /**
    238      * Get the default {@link IRunUtil} instance
    239      */
    240     @VisibleForTesting
    241     IRunUtil getRunUtil() {
    242         return RunUtil.getDefault();
    243     }
    244 
    245     /**
    246      * Allows to override the invocation settings of capture on failure by the module specific
    247      * configurations.
    248      *
    249      * @param bugreportOnFailure true to capture a bugreport on test failure. False otherwise.
    250      * @param logcatOnfailure true to capture a logcat on test failure. False otherwise.
    251      * @param screenshotOnFailure true to capture a screenshot on failure. False otherwise.
    252      */
    253     public void applyModuleConfiguration(
    254             boolean bugreportOnFailure, boolean logcatOnfailure, boolean screenshotOnFailure) {
    255         mModuleBugReportOnFailure = bugreportOnFailure;
    256         mModuleLogcatOnFailure = logcatOnfailure;
    257         mModuleScreenshotOnFailure = screenshotOnFailure;
    258     }
    259 
    260     /** Sets where the logs should be saved. */
    261     public void setLogger(ITestLogger logger) {
    262         mLogger = logger;
    263     }
    264 }
    265