Home | History | Annotate | Download | only in result
      1 /*
      2  * Copyright (C) 2011 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.result;
     17 
     18 import com.android.tradefed.device.DeviceNotAvailableException;
     19 import com.android.tradefed.device.ITestDevice;
     20 import com.android.tradefed.log.LogUtil.CLog;
     21 import com.android.tradefed.util.FileUtil;
     22 import com.android.tradefed.util.StreamUtil;
     23 
     24 import java.io.File;
     25 import java.util.Arrays;
     26 import java.util.HashMap;
     27 import java.util.HashSet;
     28 import java.util.LinkedHashMap;
     29 import java.util.LinkedList;
     30 import java.util.List;
     31 import java.util.Map;
     32 import java.util.Set;
     33 
     34 /**
     35  * A utility class that checks the device for files and sends them to
     36  * {@link ITestInvocationListener#testLog(String, LogDataType, InputStreamSource)} if found.
     37  */
     38 public class DeviceFileReporter {
     39     private final Map<String, LogDataType> mFilePatterns = new LinkedHashMap<>();
     40     private final ITestInvocationListener mListener;
     41     private final ITestDevice mDevice;
     42 
     43     /** Whether to ignore files that have already been captured by a prior Pattern */
     44     private boolean mSkipRepeatFiles = true;
     45     /** The files which have already been reported */
     46     private Set<String> mReportedFiles = new HashSet<>();
     47 
     48     /** Whether to attempt to infer data types for patterns with {@code UNKNOWN} data type */
     49     private boolean mInferDataTypes = true;
     50 
     51     private LogDataType mDefaultFileType = LogDataType.UNKNOWN;
     52 
     53     private static final Map<String, LogDataType> DATA_TYPE_REVERSE_MAP = new HashMap<>();
     54 
     55     static {
     56         // Make it easy to map backward from file extension to LogDataType
     57         for (LogDataType type : LogDataType.values()) {
     58             // Extracted extension will contain a leading dot
     59             final String ext = "." + type.getFileExt();
     60             if (DATA_TYPE_REVERSE_MAP.containsKey(ext)) {
     61                 continue;
     62             }
     63 
     64             DATA_TYPE_REVERSE_MAP.put(ext, type);
     65         }
     66     }
     67     /**
     68      * Initialize a new DeviceFileReporter with the provided {@link ITestDevice}
     69      */
     70     public DeviceFileReporter(ITestDevice device, ITestInvocationListener listener) {
     71         // Do a null check here, since otherwise that error would be asynchronous
     72         if (device == null || listener == null) {
     73             throw new NullPointerException();
     74         }
     75         mDevice = device;
     76         mListener = listener;
     77     }
     78 
     79     /**
     80      * Add patterns with the log data type set to the default.
     81      *
     82      * @param patterns a varargs array of {@link String} filename glob patterns. Should be absolute.
     83      * @see #setDefaultLogDataType
     84      */
     85     public void addPatterns(String... patterns) {
     86         addPatterns(Arrays.asList(patterns));
     87     }
     88 
     89     /**
     90      * Add patterns with the log data type set to the default.
     91      *
     92      * @param patterns a {@link List} of {@link String} filename glob patterns. Should be absolute.
     93      * @see #setDefaultLogDataType
     94      */
     95     public void addPatterns(List<String> patterns) {
     96         for (String pat : patterns) {
     97             mFilePatterns.put(pat, mDefaultFileType);
     98         }
     99     }
    100 
    101     /**
    102      * Add patterns with the respective log data types
    103      *
    104      * @param patterns a {@link Map} of {@link String} filename glob patterns to their respective
    105      *        {@link LogDataType}s.  The globs should be absolute.
    106      * @see #setDefaultLogDataType
    107      */
    108     public void addPatterns(Map<String, LogDataType> patterns) {
    109         mFilePatterns.putAll(patterns);
    110     }
    111 
    112     /**
    113      * Set the default log data type set for patterns that don't have an associated type.
    114      *
    115      * @param type the {@link LogDataType}
    116      * @see #addPatterns(List)
    117      */
    118     public void setDefaultLogDataType(LogDataType type) {
    119         if (type == null) {
    120             throw new NullPointerException();
    121         }
    122         mDefaultFileType = type;
    123     }
    124 
    125     /**
    126      * Whether or not to skip files which have already been reported.  This is only relevant when
    127      * multiple patterns are being used, and two or more of those patterns match the same file.
    128      * <p />
    129      * Note that this <emph>must only</emph> be called prior to calling {@link #run()}. Doing
    130      * otherwise will cause undefined behavior.
    131      */
    132     public void setSkipRepeatFiles(boolean skip) {
    133         mSkipRepeatFiles = skip;
    134     }
    135 
    136     /**
    137      * Whether to <emph>attempt to</emph> infer the data types of {@code UNKNOWN} files by checking
    138      * the file extensions against a list.
    139      * <p />
    140      * Note that, when enabled, these inferences will only be made for patterns with file type
    141      * {@code UNKNOWN} (which includes patterns added without a specific type, and without the)
    142      * default type having been set manually).  If the inference fails, the data type will remain
    143      * as {@code UNKNOWN}.
    144      */
    145     public void setInferUnknownDataTypes(boolean infer) {
    146         mInferDataTypes = infer;
    147     }
    148 
    149     /**
    150      * Actually search the filesystem for the specified patterns and send them to
    151      * {@link ITestInvocationListener#testLog} if found
    152      */
    153     public List<String> run() throws DeviceNotAvailableException {
    154         List<String> filenames = new LinkedList<>();
    155         CLog.d(String.format("Analyzing %d patterns.", mFilePatterns.size()));
    156         for (Map.Entry<String, LogDataType> pat : mFilePatterns.entrySet()) {
    157             final String searchCmd = String.format("ls %s", pat.getKey());
    158             final String fileList = mDevice.executeShellCommand(searchCmd);
    159 
    160             for (String filename : fileList.split("\r?\n")) {
    161                 filename = filename.trim();
    162                 if (filename.isEmpty() || filename.endsWith(": No such file or directory")) {
    163                     continue;
    164                 }
    165                 if (mSkipRepeatFiles && mReportedFiles.contains(filename)) {
    166                     CLog.v("Skipping already-reported file %s", filename);
    167                     continue;
    168                 }
    169 
    170                 File file = null;
    171                 InputStreamSource iss = null;
    172                 try {
    173                     CLog.d("Trying to pull file '%s' from device %s", filename,
    174                         mDevice.getSerialNumber());
    175                     file = mDevice.pullFile(filename);
    176                     iss = createIssForFile(file);
    177                     final LogDataType type = getDataType(filename, pat.getValue());
    178                     CLog.d("Local file %s has size %d and type %s", file, file.length(),
    179                         type.getFileExt());
    180                     mListener.testLog(filename, type, iss);
    181                     filenames.add(filename);
    182                     mReportedFiles.add(filename);
    183                 } finally {
    184                     StreamUtil.cancel(iss);
    185                     iss = null;
    186                     FileUtil.deleteFile(file);
    187                 }
    188             }
    189         }
    190         return filenames;
    191     }
    192 
    193     /**
    194      * Returns the data type to use for a given file.  Will attempt to infer the data type from the
    195      * file's extension IFF inferences are enabled, and the current data type is {@code UNKNOWN}.
    196      */
    197     LogDataType getDataType(String filename, LogDataType defaultType) {
    198         if (!mInferDataTypes) return defaultType;
    199         if (!LogDataType.UNKNOWN.equals(defaultType)) return defaultType;
    200 
    201         CLog.d("Running type inference for file %s with default type %s", filename, defaultType);
    202         String ext = FileUtil.getExtension(filename);
    203         CLog.v("Found raw extension \"%s\"", ext);
    204 
    205         // Normalize the extension
    206         if (ext == null) return defaultType;
    207         ext = ext.toLowerCase();
    208 
    209         if (DATA_TYPE_REVERSE_MAP.containsKey(ext)) {
    210             final LogDataType newType = DATA_TYPE_REVERSE_MAP.get(ext);
    211             CLog.d("Inferred data type %s", newType);
    212             return newType;
    213         } else {
    214             CLog.v("Failed to find a reverse map for extension \"%s\"", ext);
    215             return defaultType;
    216         }
    217     }
    218 
    219     /**
    220      * Create an {@link InputStreamSource} for a file
    221      *
    222      * <p>Exposed for unit testing
    223      */
    224     InputStreamSource createIssForFile(File file) {
    225         return new FileInputStreamSource(file);
    226     }
    227 }
    228