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.ddmlib.Log;
     19 import com.android.cts.tradefed.result.TestLog.TestLogType;
     20 
     21 import org.kxml2.io.KXmlSerializer;
     22 import org.xmlpull.v1.XmlPullParser;
     23 import org.xmlpull.v1.XmlPullParserException;
     24 
     25 import java.io.IOException;
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 /**
     30  * Data structure that represents a "Test" result XML element.
     31  */
     32 class Test extends AbstractXmlPullParser {
     33     static final String TAG = "Test";
     34     private static final String NAME_ATTR = "name";
     35     private static final String MESSAGE_ATTR = "message";
     36     private static final String ENDTIME_ATTR = "endtime";
     37     private static final String STARTTIME_ATTR = "starttime";
     38     private static final String RESULT_ATTR = "result";
     39     private static final String SCENE_TAG = "FailedScene";
     40     private static final String STACK_TAG = "StackTrace";
     41     private static final String SUMMARY_TAG = "Summary";
     42     private static final String DETAILS_TAG = "Details";
     43     private static final String VALUEARRAY_TAG = "ValueArray";
     44     private static final String VALUE_TAG = "Value";
     45     private static final String TARGET_ATTR = "target";
     46     private static final String SCORETYPE_ATTR = "scoreType";
     47     private static final String UNIT_ATTR = "unit";
     48     private static final String SOURCE_ATTR = "source";
     49     // separators for the message
     50     private static final String LOG_SEPARATOR = "\\+\\+\\+";
     51     private static final String LOG_ELEM_SEPARATOR = "\\|";
     52 
     53     private String mName;
     54     private CtsTestStatus mResult;
     55     private String mStartTime;
     56     private String mEndTime;
     57     private String mMessage;
     58     private String mStackTrace;
     59     // summary and details passed from cts
     60     private String mSummary;
     61     private String mDetails;
     62 
     63     /**
     64      * Log info for this test like a logcat dump or bugreport.
     65      * Use *Locked methods instead of mutating this directly.
     66      */
     67     private List<TestLog> mTestLogs;
     68 
     69     /**
     70      * Create an empty {@link Test}
     71      */
     72     public Test() {
     73     }
     74 
     75     /**
     76      * Create a {@link Test} from a {@link TestResult}.
     77      *
     78      * @param name
     79      */
     80     public Test(String name) {
     81         mName = name;
     82         mResult = CtsTestStatus.NOT_EXECUTED;
     83         mStartTime = TimeUtil.getTimestamp();
     84         updateEndTime();
     85     }
     86 
     87     /**
     88      * Add a test log to this Test.
     89      */
     90     public void addTestLog(TestLog testLog) {
     91         addTestLogLocked(testLog);
     92     }
     93 
     94     /**
     95      * Set the name of this {@link Test}
     96      */
     97     public void setName(String name) {
     98         mName = name;
     99     }
    100 
    101     /**
    102      * Get the name of this {@link Test}
    103      */
    104     public String getName() {
    105         return mName;
    106     }
    107 
    108     public CtsTestStatus getResult() {
    109         return mResult;
    110     }
    111 
    112     public String getMessage() {
    113         return mMessage;
    114     }
    115 
    116     public void setMessage(String message) {
    117         mMessage = message;
    118     }
    119 
    120     public String getStartTime() {
    121         return mStartTime;
    122     }
    123 
    124     public String getEndTime() {
    125         return mEndTime;
    126     }
    127 
    128     public String getStackTrace() {
    129         return mStackTrace;
    130     }
    131 
    132     public void setStackTrace(String stackTrace) {
    133 
    134         mStackTrace = sanitizeStackTrace(stackTrace);
    135         mMessage = getFailureMessageFromStackTrace(mStackTrace);
    136     }
    137 
    138     public String getSummary() {
    139         return mSummary;
    140     }
    141 
    142     public void setSummary(String summary) {
    143         mSummary = summary;
    144     }
    145 
    146     public String getDetails() {
    147         return mDetails;
    148     }
    149 
    150     public void setDetails(String details) {
    151         mDetails = details;
    152     }
    153 
    154     public void updateEndTime() {
    155         mEndTime = TimeUtil.getTimestamp();
    156     }
    157 
    158     public void setResultStatus(CtsTestStatus status) {
    159         mResult = status;
    160     }
    161 
    162     /**
    163      * Serialize this object and all its contents to XML.
    164      *
    165      * @param serializer
    166      * @throws IOException
    167      */
    168     public void serialize(KXmlSerializer serializer)
    169             throws IOException {
    170         serializer.startTag(CtsXmlResultReporter.ns, TAG);
    171         serializer.attribute(CtsXmlResultReporter.ns, NAME_ATTR, getName());
    172         serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult.getValue());
    173         serializer.attribute(CtsXmlResultReporter.ns, STARTTIME_ATTR, mStartTime);
    174         serializer.attribute(CtsXmlResultReporter.ns, ENDTIME_ATTR, mEndTime);
    175 
    176         serializeTestLogsLocked(serializer);
    177 
    178         if (mMessage != null) {
    179             serializer.startTag(CtsXmlResultReporter.ns, SCENE_TAG);
    180             serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, mMessage);
    181             if (mStackTrace != null) {
    182                 serializer.startTag(CtsXmlResultReporter.ns, STACK_TAG);
    183                 serializer.text(mStackTrace);
    184                 serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
    185             }
    186             serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
    187         }
    188         if (mSummary != null) {
    189             // <Summary message = "screen copies per sec" scoretype="higherBetter" unit="fps">
    190             // 23938.82978723404</Summary>
    191             PerfResultSummary summary = parseSummary(mSummary);
    192             if (summary != null) {
    193                 serializer.startTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
    194                 serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, summary.mMessage);
    195                 if (summary.mTarget.length() != 0 && !summary.mTarget.equals(" ")) {
    196                     serializer.attribute(CtsXmlResultReporter.ns, TARGET_ATTR, summary.mTarget);
    197                 }
    198                 serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, summary.mType);
    199                 serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, summary.mUnit);
    200                 serializer.text(summary.mValue);
    201                 serializer.endTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
    202                 // add details only if summary is present
    203                 // <Details>
    204                 //   <ValueArray source=com.android.cts.dram.BandwidthTest#doRunMemcpy:98
    205                 //                    message=measure1 unit="ms" scoretype="higherBetter">
    206                 //     <Value>0.0</Value>
    207                 //     <Value>0.1</Value>
    208                 //   </ValueArray>
    209                 // </Details>
    210                 if (mDetails != null) {
    211                     PerfResultDetail[] ds = parseDetails(mDetails);
    212                     serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
    213                         for (PerfResultDetail d : ds) {
    214                             if (d == null) {
    215                                 continue;
    216                             }
    217                             serializer.startTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
    218                             serializer.attribute(CtsXmlResultReporter.ns, SOURCE_ATTR, d.mSource);
    219                             serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR,
    220                                     d.mMessage);
    221                             serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, d.mType);
    222                             serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, d.mUnit);
    223                             for (String v : d.mValues) {
    224                                 if (v == null) {
    225                                     continue;
    226                                 }
    227                                 serializer.startTag(CtsXmlResultReporter.ns, VALUE_TAG);
    228                                 serializer.text(v);
    229                                 serializer.endTag(CtsXmlResultReporter.ns, VALUE_TAG);
    230                             }
    231                             serializer.endTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
    232                         }
    233                     serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
    234                 }
    235             }
    236         }
    237         serializer.endTag(CtsXmlResultReporter.ns, TAG);
    238     }
    239 
    240     /**
    241      *  class containing performance result.
    242      */
    243     public static class PerfResultCommon {
    244         public String mMessage;
    245         public String mType;
    246         public String mUnit;
    247     }
    248 
    249     private class PerfResultSummary extends PerfResultCommon {
    250         public String mTarget;
    251         public String mValue;
    252     }
    253 
    254     private class PerfResultDetail extends PerfResultCommon {
    255         public String mSource;
    256         public String[] mValues;
    257     }
    258 
    259     private PerfResultSummary parseSummary(String summary) {
    260         String[] elems = summary.split(LOG_ELEM_SEPARATOR);
    261         PerfResultSummary r = new PerfResultSummary();
    262         if (elems.length < 5) {
    263             Log.w(TAG, "wrong message " + summary);
    264             return null;
    265         }
    266         r.mMessage = elems[0];
    267         r.mTarget = elems[1];
    268         r.mType = elems[2];
    269         r.mUnit = elems[3];
    270         r.mValue = elems[4];
    271         return r;
    272     }
    273 
    274     private PerfResultDetail[] parseDetails(String details) {
    275         String[] arrays = details.split(LOG_SEPARATOR);
    276         PerfResultDetail[] rs = new PerfResultDetail[arrays.length];
    277         for (int i = 0; i < arrays.length; i++) {
    278             String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
    279             if (elems.length < 5) {
    280                 Log.w(TAG, "wrong message " + arrays[i]);
    281                 continue;
    282             }
    283             PerfResultDetail r = new PerfResultDetail();
    284             r.mSource = elems[0];
    285             r.mMessage = elems[1];
    286             r.mType = elems[2];
    287             r.mUnit = elems[3];
    288             r.mValues = elems[4].split(" ");
    289             rs[i] = r;
    290         }
    291         return rs;
    292     }
    293 
    294     /**
    295      * Strip out any invalid XML characters that might cause the report to be unviewable.
    296      * http://www.w3.org/TR/REC-xml/#dt-character
    297      */
    298     private static String sanitizeStackTrace(String trace) {
    299         if (trace != null) {
    300             return trace.replaceAll("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
    301         } else {
    302             return null;
    303         }
    304     }
    305 
    306     /**
    307      * Gets the failure message to show from the stack trace.
    308      * <p/>
    309      * Exposed for unit testing
    310      *
    311      * @param stack the full stack trace
    312      * @return the failure message
    313      */
    314     static String getFailureMessageFromStackTrace(String stack) {
    315         // return the first two lines of stack as failure message
    316         int endPoint = stack.indexOf('\n');
    317         if (endPoint != -1) {
    318             int nextLine = stack.indexOf('\n', endPoint + 1);
    319             if (nextLine != -1) {
    320                 return stack.substring(0, nextLine);
    321             }
    322         }
    323         return stack;
    324     }
    325 
    326     /**
    327      * Populates this class with test result data parsed from XML.
    328      *
    329      * @param parser the {@link XmlPullParser}. Expected to be pointing at start
    330      *            of a Test tag
    331      */
    332     @Override
    333     void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
    334         if (!parser.getName().equals(TAG)) {
    335             throw new XmlPullParserException(String.format(
    336                     "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
    337         }
    338         setName(getAttribute(parser, NAME_ATTR));
    339         mResult = CtsTestStatus.getStatus(getAttribute(parser, RESULT_ATTR));
    340         mStartTime = getAttribute(parser, STARTTIME_ATTR);
    341         mEndTime = getAttribute(parser, ENDTIME_ATTR);
    342 
    343         int eventType = parser.next();
    344         while (eventType != XmlPullParser.END_DOCUMENT) {
    345             if (eventType == XmlPullParser.START_TAG && parser.getName().equals(SCENE_TAG)) {
    346                 mMessage = getAttribute(parser, MESSAGE_ATTR);
    347             } else if (eventType == XmlPullParser.START_TAG && parser.getName().equals(STACK_TAG)) {
    348                 mStackTrace = parser.nextText();
    349             } else if (eventType == XmlPullParser.START_TAG && TestLog.isTag(parser.getName())) {
    350                 parseTestLog(parser);
    351             } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
    352                 return;
    353             }
    354             eventType = parser.next();
    355         }
    356     }
    357 
    358     /** Parse a TestLog entry from the parser positioned at a TestLog tag. */
    359     private void parseTestLog(XmlPullParser parser) throws XmlPullParserException{
    360         TestLog log = TestLog.fromXml(parser);
    361         if (log == null) {
    362             throw new XmlPullParserException("invalid XML: bad test log tag");
    363         }
    364         addTestLog(log);
    365     }
    366 
    367     /** Add a TestLog to the test in a thread safe manner. */
    368     private synchronized void addTestLogLocked(TestLog testLog) {
    369         if (mTestLogs == null) {
    370             mTestLogs = new ArrayList<>(TestLogType.values().length);
    371         }
    372         mTestLogs.add(testLog);
    373     }
    374 
    375     /** Serialize the TestLogs of this test in a thread safe manner. */
    376     private synchronized void serializeTestLogsLocked(KXmlSerializer serializer) throws IOException {
    377         if (mTestLogs != null) {
    378             for (TestLog log : mTestLogs) {
    379                 log.serialize(serializer);
    380             }
    381         }
    382     }
    383 }
    384