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      */
Test()72     public Test() {
73     }
74 
75     /**
76      * Create a {@link Test} from a {@link TestResult}.
77      *
78      * @param name
79      */
Test(String name)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      */
addTestLog(TestLog testLog)90     public void addTestLog(TestLog testLog) {
91         addTestLogLocked(testLog);
92     }
93 
94     /**
95      * Set the name of this {@link Test}
96      */
setName(String name)97     public void setName(String name) {
98         mName = name;
99     }
100 
101     /**
102      * Get the name of this {@link Test}
103      */
getName()104     public String getName() {
105         return mName;
106     }
107 
getResult()108     public CtsTestStatus getResult() {
109         return mResult;
110     }
111 
getMessage()112     public String getMessage() {
113         return mMessage;
114     }
115 
setMessage(String message)116     public void setMessage(String message) {
117         mMessage = message;
118     }
119 
getStartTime()120     public String getStartTime() {
121         return mStartTime;
122     }
123 
getEndTime()124     public String getEndTime() {
125         return mEndTime;
126     }
127 
getStackTrace()128     public String getStackTrace() {
129         return mStackTrace;
130     }
131 
setStackTrace(String stackTrace)132     public void setStackTrace(String stackTrace) {
133 
134         mStackTrace = sanitizeStackTrace(stackTrace);
135         mMessage = getFailureMessageFromStackTrace(mStackTrace);
136     }
137 
getSummary()138     public String getSummary() {
139         return mSummary;
140     }
141 
setSummary(String summary)142     public void setSummary(String summary) {
143         mSummary = summary;
144     }
145 
getDetails()146     public String getDetails() {
147         return mDetails;
148     }
149 
setDetails(String details)150     public void setDetails(String details) {
151         mDetails = details;
152     }
153 
updateEndTime()154     public void updateEndTime() {
155         mEndTime = TimeUtil.getTimestamp();
156     }
157 
setResultStatus(CtsTestStatus status)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      */
serialize(KXmlSerializer serializer)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 
parseSummary(String summary)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 
parseDetails(String details)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      */
sanitizeStackTrace(String trace)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      */
getFailureMessageFromStackTrace(String stack)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
parse(XmlPullParser parser)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. */
parseTestLog(XmlPullParser parser)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. */
addTestLogLocked(TestLog testLog)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. */
serializeTestLogsLocked(KXmlSerializer serializer)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