Test.java revision 992b459293c4dcae2a12cdf5923e3e26a476bd1a
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 */
16package com.android.cts.tradefed.result;
17
18import com.android.ddmlib.Log;
19import com.android.cts.tradefed.result.TestLog.TestLogType;
20
21import org.kxml2.io.KXmlSerializer;
22import org.xmlpull.v1.XmlPullParser;
23import org.xmlpull.v1.XmlPullParserException;
24
25import java.io.IOException;
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Data structure that represents a "Test" result XML element.
31 */
32class 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