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