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.cts.tradefed.result;
     17 
     18 import com.android.tradefed.log.LogUtil.CLog;
     19 
     20 import org.kxml2.io.KXmlSerializer;
     21 import org.xmlpull.v1.XmlPullParser;
     22 import org.xmlpull.v1.XmlPullParserException;
     23 
     24 import android.tests.getinfo.DeviceInfoConstants;
     25 
     26 import java.io.IOException;
     27 import java.util.Collections;
     28 import java.util.HashMap;
     29 import java.util.HashSet;
     30 import java.util.Map;
     31 import java.util.Set;
     32 
     33 /**
     34  * Data structure for the device info collected by CTS.
     35  * <p/>
     36  * Provides methods to serialize and deserialize from XML, as well as checks for consistency
     37  * when multiple devices are used to generate the report.
     38  */
     39 class DeviceInfoResult extends AbstractXmlPullParser {
     40     static final String TAG = "DeviceInfo";
     41     private static final String ns = CtsXmlResultReporter.ns;
     42     static final String BUILD_TAG = "BuildInfo";
     43     private static final String PHONE_TAG = "PhoneSubInfo";
     44     private static final String SCREEN_TAG = "Screen";
     45 
     46     private static final String FEATURE_INFO_TAG = "FeatureInfo";
     47     private static final String FEATURE_TAG = "Feature";
     48     private static final String FEATURE_ATTR_DELIM = ":";
     49     private static final String FEATURE_DELIM = ";";
     50 
     51     private static final String OPENGL_TEXTURE_FORMATS_INFO_TAG =
     52             "OpenGLCompressedTextureFormatsInfo";
     53     private static final String OPENGL_TEXTURE_FORMAT_TAG = "TextureFormat";
     54     private static final String OPENGL_TEXTURE_FORMAT_DELIM = ";";
     55 
     56     private static final String OPENGL_EXTENSIONS_TAG = "OpenGlExtensions";
     57     private static final String OPENGL_EXTENSION_TAG = "OpenGlExtension";
     58     private static final String OPENGL_EXTENSION_DELIM = ";";
     59 
     60     private static final String SYSLIB_INFO_TAG = "SystemLibrariesInfo";
     61     private static final String SYSLIB_TAG = "Library";
     62     private static final String SYSLIB_DELIM = ";";
     63 
     64     private static final String PROCESS_INFO_TAG = "ProcessInfo";
     65     private static final String PROCESS_TAG = "Process";
     66     private static final String PROCESS_DELIM = ";";
     67     private static final String PROCESS_ATTR_DELIM = ":";
     68 
     69     private Map<String, String> mMetrics = new HashMap<String, String>();
     70 
     71     /**
     72      * Serialize this object and all its contents to XML.
     73      *
     74      * @param serializer
     75      * @throws IOException
     76      */
     77     public void serialize(KXmlSerializer serializer) throws IOException {
     78         serializer.startTag(ns, TAG);
     79 
     80         if (mMetrics.isEmpty()) {
     81             // this might be expected, if device info collection was turned off
     82             CLog.d("Could not find device info");
     83             serializer.endTag(ns, TAG);
     84             return;
     85         }
     86 
     87         // Extract metrics that need extra handling, and then dump the remainder into BuildInfo
     88         Map<String, String> metricsCopy = new HashMap<String, String>(mMetrics);
     89         serializer.startTag(ns, SCREEN_TAG);
     90         serializer.attribute(ns, DeviceInfoConstants.RESOLUTION,
     91                 getMetric(metricsCopy, DeviceInfoConstants.RESOLUTION));
     92         serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY,
     93                 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY));
     94         serializer.attribute(ns, DeviceInfoConstants.SCREEN_DENSITY_BUCKET,
     95                 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_DENSITY_BUCKET));
     96         serializer.attribute(ns, DeviceInfoConstants.SCREEN_SIZE,
     97                 getMetric(metricsCopy, DeviceInfoConstants.SCREEN_SIZE));
     98         serializer.endTag(ns, SCREEN_TAG);
     99 
    100         serializer.startTag(ns, PHONE_TAG);
    101         serializer.attribute(ns, DeviceInfoConstants.PHONE_NUMBER,
    102                 getMetric(metricsCopy, DeviceInfoConstants.PHONE_NUMBER));
    103         serializer.endTag(ns, PHONE_TAG);
    104 
    105         String featureData = getMetric(metricsCopy, DeviceInfoConstants.FEATURES);
    106         String processData = getMetric(metricsCopy, DeviceInfoConstants.PROCESSES);
    107         String sysLibData = getMetric(metricsCopy, DeviceInfoConstants.SYS_LIBRARIES);
    108         String textureData = getMetric(metricsCopy,
    109                 DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS);
    110         String openGlExtensionData = getMetric(metricsCopy,
    111                 DeviceInfoConstants.OPEN_GL_EXTENSIONS);
    112 
    113         // dump the remaining metrics without translation
    114         serializer.startTag(ns, BUILD_TAG);
    115         for (Map.Entry<String, String> metricEntry : metricsCopy.entrySet()) {
    116             serializer.attribute(ns, metricEntry.getKey(), metricEntry.getValue());
    117         }
    118         serializer.endTag(ns, BUILD_TAG);
    119 
    120         serializeFeatureInfo(serializer, featureData);
    121         serializeProcessInfo(serializer, processData);
    122         serializeSystemLibrariesInfo(serializer, sysLibData);
    123         serializeOpenGLCompressedTextureFormatsInfo(serializer, textureData);
    124         serializeOpenGLExtensions(serializer, openGlExtensionData);
    125         // End
    126         serializer.endTag(ns, TAG);
    127     }
    128 
    129     /**
    130      * Fetch and remove given metric from hashmap.
    131      *
    132      * @return the metric value or empty string if it was not present in map.
    133      */
    134     private String getMetric(Map<String, String> metrics, String metricName ) {
    135         String value = metrics.remove(metricName);
    136         if (value == null) {
    137             value = "";
    138         }
    139         return value;
    140     }
    141 
    142     private void serializeFeatureInfo(KXmlSerializer serializer, String featureData)
    143             throws IOException {
    144         serialize(serializer, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM,
    145                 featureData, "name", "type", "available");
    146     }
    147 
    148     private void serializeProcessInfo(KXmlSerializer serializer, String rootProcesses)
    149             throws IOException {
    150         serialize(serializer, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM, PROCESS_ATTR_DELIM,
    151                 rootProcesses, "name", "uid");
    152     }
    153 
    154     private void serializeOpenGLCompressedTextureFormatsInfo(KXmlSerializer serializer,
    155             String formats) throws IOException {
    156         serialize(serializer, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG,
    157                 OPENGL_TEXTURE_FORMAT_DELIM, null, formats, "name");
    158     }
    159 
    160     private void serializeOpenGLExtensions(KXmlSerializer serializer, String extensions)
    161             throws IOException {
    162         serialize(serializer, OPENGL_EXTENSIONS_TAG, OPENGL_EXTENSION_TAG,
    163                 OPENGL_EXTENSION_DELIM, null, extensions, "name");
    164     }
    165 
    166     private void serializeSystemLibrariesInfo(KXmlSerializer serializer, String libs)
    167             throws IOException {
    168         serialize(serializer, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, libs, "name");
    169     }
    170 
    171     /**
    172      * Serializes a XML structure where there is an outer tag with tags inside it.
    173      *
    174      * <pre>
    175      *   Input: value1:value2;value3:value4
    176      *
    177      *   Output:
    178      *   <OuterTag>
    179      *     <SubTag attr1="value1" attr2="value2" />
    180      *     <SubTag attr1="value3" attr2="value4" />
    181      *   </OuterTag>
    182      * </pre>
    183      *
    184      * @param serializer to do it
    185      * @param tag would be "OuterTag"
    186      * @param subTag would be "SubTag"
    187      * @param delim would be ";"
    188      * @param attrDelim would be ":" in the example but can be null if only one attrName given
    189      * @param data would be "value1:value2;value3:value4"
    190      * @param attrNames would be an array with "attr1", "attr2"
    191      * @throws IOException if there is a problem
    192      */
    193     private void serialize(KXmlSerializer serializer, String tag, String subTag,
    194             String delim, String attrDelim, String data, String... attrNames) throws IOException {
    195         serializer.startTag(ns, tag);
    196 
    197         if (data == null) {
    198             data = "";
    199         }
    200 
    201         String[] values = data.split(delim);
    202         for (String value : values) {
    203             if (!value.isEmpty()) {
    204                 String[] attrValues = attrDelim != null ? value.split(attrDelim) : new String[] {value};
    205                 if (attrValues.length == attrNames.length) {
    206                     serializer.startTag(ns, subTag);
    207                     for (int i = 0; i < attrNames.length; i++) {
    208                         serializer.attribute(ns, attrNames[i], attrValues[i]);
    209                     }
    210                     serializer.endTag(ns,  subTag);
    211                 }
    212             }
    213         }
    214 
    215         serializer.endTag(ns, tag);
    216     }
    217 
    218     /**
    219      * Populates this class with package result data parsed from XML.
    220      *
    221      * @param parser the {@link XmlPullParser}. Expected to be pointing at start
    222      *            of a {@link #TAG}
    223      */
    224     @Override
    225     void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
    226         if (!parser.getName().equals(TAG)) {
    227             throw new XmlPullParserException(String.format(
    228                     "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
    229         }
    230         int eventType = parser.getEventType();
    231         while (eventType != XmlPullParser.END_DOCUMENT) {
    232             if (eventType == XmlPullParser.START_TAG) {
    233                 if (parser.getName().equals(SCREEN_TAG) ||
    234                         parser.getName().equals(PHONE_TAG) ||
    235                         parser.getName().equals(BUILD_TAG)) {
    236                     addMetricsFromAttributes(parser);
    237                 } else if (parser.getName().equals(FEATURE_INFO_TAG)) {
    238                     mMetrics.put(DeviceInfoConstants.FEATURES, parseFeatures(parser));
    239                 } else if (parser.getName().equals(PROCESS_INFO_TAG)) {
    240                     mMetrics.put(DeviceInfoConstants.PROCESSES, parseProcess(parser));
    241                 } else if (parser.getName().equals(SYSLIB_INFO_TAG)) {
    242                     mMetrics.put(DeviceInfoConstants.SYS_LIBRARIES, parseSystemLibraries(parser));
    243                 } else if (parser.getName().equals(OPENGL_TEXTURE_FORMATS_INFO_TAG)) {
    244                     mMetrics.put(DeviceInfoConstants.OPEN_GL_COMPRESSED_TEXTURE_FORMATS,
    245                             parseOpenGLCompressedTextureFormats(parser));
    246                 }
    247             } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
    248                 return;
    249             }
    250             eventType = parser.next();
    251         }
    252     }
    253 
    254     private String parseFeatures(XmlPullParser parser) throws XmlPullParserException, IOException {
    255         return parseTag(parser, FEATURE_INFO_TAG, FEATURE_TAG, FEATURE_DELIM, FEATURE_ATTR_DELIM,
    256                 "name", "type", "available");
    257     }
    258 
    259     private String parseProcess(XmlPullParser parser) throws XmlPullParserException, IOException {
    260         return parseTag(parser, PROCESS_INFO_TAG, PROCESS_TAG, PROCESS_DELIM,
    261                 PROCESS_ATTR_DELIM, "name", "uid");
    262     }
    263 
    264     private String parseOpenGLCompressedTextureFormats(XmlPullParser parser)
    265             throws XmlPullParserException, IOException {
    266         return parseTag(parser, OPENGL_TEXTURE_FORMATS_INFO_TAG, OPENGL_TEXTURE_FORMAT_TAG,
    267                 OPENGL_TEXTURE_FORMAT_DELIM, null, "name");
    268     }
    269 
    270     private String parseSystemLibraries(XmlPullParser parser)
    271             throws XmlPullParserException, IOException {
    272         return parseTag(parser, SYSLIB_INFO_TAG, SYSLIB_TAG, SYSLIB_DELIM, null, "name");
    273     }
    274 
    275     /**
    276      * Converts XML into a flattened string.
    277      *
    278      * <pre>
    279      *   Input:
    280      *   <OuterTag>
    281      *     <SubTag attr1="value1" attr2="value2" />
    282      *     <SubTag attr1="value3" attr2="value4" />
    283      *   </OuterTag>
    284      *
    285      *   Output: value1:value2;value3:value4
    286      * </pre>
    287      *
    288      * @param parser that parses the xml
    289      * @param tag like "OuterTag"
    290      * @param subTag like "SubTag"
    291      * @param delim like ";"
    292      * @param attrDelim like ":" or null if tehre is only one attribute
    293      * @param attrNames like "attr1", "attr2"
    294      * @return flattened string like "value1:value2;value3:value4"
    295      * @throws XmlPullParserException
    296      * @throws IOException
    297      */
    298     private String parseTag(XmlPullParser parser, String tag, String subTag, String delim,
    299             String attrDelim, String... attrNames) throws XmlPullParserException, IOException {
    300         if (!parser.getName().equals(tag)) {
    301             throw new XmlPullParserException(String.format(
    302                     "invalid XML: Expected %s tag but received %s", tag,
    303                     parser.getName()));
    304         }
    305         StringBuilder flattened = new StringBuilder();
    306 
    307         for (int eventType = parser.getEventType();
    308                 eventType != XmlPullParser.END_DOCUMENT;
    309                 eventType = parser.next()) {
    310 
    311             if (eventType == XmlPullParser.START_TAG && parser.getName().equals(subTag)) {
    312                 for (int i = 0; i < attrNames.length; i++) {
    313                     flattened.append(getAttribute(parser, attrNames[i]));
    314                     if (i + 1 < attrNames.length) {
    315                         flattened.append(attrDelim);
    316                     }
    317                 }
    318                 flattened.append(delim);
    319             } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(tag)) {
    320                 break;
    321             }
    322         }
    323 
    324         return flattened.toString();
    325     }
    326 
    327     /**
    328      * Adds all attributes from the current XML tag to metrics as name-value pairs
    329      */
    330     private void addMetricsFromAttributes(XmlPullParser parser) {
    331         int attrCount = parser.getAttributeCount();
    332         for (int i = 0; i < attrCount; i++) {
    333             mMetrics.put(parser.getAttributeName(i), parser.getAttributeValue(i));
    334         }
    335     }
    336 
    337     /**
    338      * Populate the device info metrics with values collected from device.
    339      * <p/>
    340      * Check that the provided device info metrics are consistent with the currently stored metrics.
    341      * If any inconsistencies occur, logs errors and stores error messages in the metrics map
    342      */
    343     public void populateMetrics(Map<String, String> metrics) {
    344         if (mMetrics.isEmpty()) {
    345             // no special processing needed, no existing metrics
    346             mMetrics.putAll(metrics);
    347             return;
    348         }
    349         Map<String, String> metricsCopy = new HashMap<String, String>(
    350                 metrics);
    351         // add values for metrics that might be different across runs
    352         combineMetrics(metricsCopy, DeviceInfoConstants.PHONE_NUMBER, DeviceInfoConstants.IMSI,
    353                 DeviceInfoConstants.IMSI, DeviceInfoConstants.SERIAL_NUMBER);
    354 
    355         // ensure all the metrics we expect to be identical actually are
    356         checkMetrics(metricsCopy, DeviceInfoConstants.BUILD_FINGERPRINT,
    357                 DeviceInfoConstants.BUILD_MODEL, DeviceInfoConstants.BUILD_BRAND,
    358                 DeviceInfoConstants.BUILD_MANUFACTURER, DeviceInfoConstants.BUILD_BOARD,
    359                 DeviceInfoConstants.BUILD_DEVICE, DeviceInfoConstants.PRODUCT_NAME,
    360                 DeviceInfoConstants.BUILD_ABI, DeviceInfoConstants.BUILD_ABI2,
    361                 DeviceInfoConstants.BUILD_ABIS, DeviceInfoConstants.BUILD_ABIS_32,
    362                 DeviceInfoConstants.BUILD_ABIS_64, DeviceInfoConstants.SCREEN_SIZE);
    363     }
    364 
    365     private void combineMetrics(Map<String, String> metrics, String... keysToCombine) {
    366         for (String combineKey : keysToCombine) {
    367             String currentKeyValue = mMetrics.get(combineKey);
    368             String valueToAdd = metrics.remove(combineKey);
    369             if (valueToAdd != null) {
    370                 if (currentKeyValue == null) {
    371                     // strange - no existing value. Can occur during unit testing
    372                     mMetrics.put(combineKey, valueToAdd);
    373                 } else if (!currentKeyValue.equals(valueToAdd)) {
    374                     // new value! store a comma separated list
    375                     valueToAdd = String.format("%s,%s", currentKeyValue, valueToAdd);
    376                     mMetrics.put(combineKey, valueToAdd);
    377                 } else {
    378                     // ignore, current value is same as existing
    379                 }
    380 
    381             } else {
    382                 CLog.d("Missing metric %s", combineKey);
    383             }
    384         }
    385     }
    386 
    387     private void checkMetrics(Map<String, String> metrics, String... keysToCheck) {
    388         Set<String> keyCheckSet = new HashSet<String>();
    389         Collections.addAll(keyCheckSet, keysToCheck);
    390         for (Map.Entry<String, String> metricEntry : metrics.entrySet()) {
    391             String currentValue = mMetrics.get(metricEntry.getKey());
    392             if (keyCheckSet.contains(metricEntry.getKey()) && currentValue != null
    393                     && !metricEntry.getValue().equals(currentValue)) {
    394                 CLog.e("Inconsistent info collected from devices. "
    395                         + "Current result has %s='%s', Received '%s'. Are you sharding or " +
    396                         "resuming a test run across different devices and/or builds?",
    397                         metricEntry.getKey(), currentValue, metricEntry.getValue());
    398                 mMetrics.put(metricEntry.getKey(),
    399                         String.format("ERROR: Inconsistent results: %s, %s",
    400                                 metricEntry.getValue(), currentValue));
    401             } else {
    402                 mMetrics.put(metricEntry.getKey(), metricEntry.getValue());
    403             }
    404         }
    405     }
    406 
    407     /**
    408      * Return the currently stored metrics.
    409      * <p/>
    410      * Exposed for unit testing.
    411      */
    412     Map<String, String> getMetrics() {
    413         return mMetrics;
    414     }
    415 }
    416