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.compatibility.common.util.MetricsXmlSerializer;
19 import com.android.compatibility.common.util.ReportLog;
20 import com.android.cts.tradefed.result.TestLog.TestLogType;
21 
22 import org.kxml2.io.KXmlSerializer;
23 import org.xmlpull.v1.XmlPullParser;
24 import org.xmlpull.v1.XmlPullParserException;
25 
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * Data structure that represents a "Test" result XML element.
32  */
33 class Test extends AbstractXmlPullParser {
34     static final String TAG = "Test";
35     private static final String NAME_ATTR = "name";
36     private static final String MESSAGE_ATTR = "message";
37     private static final String ENDTIME_ATTR = "endtime";
38     private static final String STARTTIME_ATTR = "starttime";
39     private static final String RESULT_ATTR = "result";
40     private static final String SCENE_TAG = "FailedScene";
41     private static final String STACK_TAG = "StackTrace";
42     private String mName;
43     private CtsTestStatus mResult;
44     private String mStartTime;
45     private String mEndTime;
46     private String mMessage;
47     private String mStackTrace;
48     private ReportLog mReport;
49 
50     /**
51      * Log info for this test like a logcat dump or bugreport.
52      * Use *Locked methods instead of mutating this directly.
53      */
54     private List<TestLog> mTestLogs;
55 
56     /**
57      * Create an empty {@link Test}
58      */
Test()59     public Test() {
60     }
61 
62     /**
63      * Create a {@link Test}.
64      *
65      * @param name
66      */
Test(String name)67     public Test(String name) {
68         mName = name;
69         mResult = CtsTestStatus.NOT_EXECUTED;
70         mStartTime = TimeUtil.getTimestamp();
71         updateEndTime();
72     }
73 
74     /**
75      * Add a test log to this Test.
76      */
addTestLog(TestLog testLog)77     public void addTestLog(TestLog testLog) {
78         addTestLogLocked(testLog);
79     }
80 
81     /**
82      * Set the name of this {@link Test}
83      */
setName(String name)84     public void setName(String name) {
85         mName = name;
86     }
87 
88     /**
89      * Get the name of this {@link Test}
90      */
getName()91     public String getName() {
92         return mName;
93     }
94 
getResult()95     public CtsTestStatus getResult() {
96         return mResult;
97     }
98 
getMessage()99     public String getMessage() {
100         return mMessage;
101     }
102 
setMessage(String message)103     public void setMessage(String message) {
104         mMessage = message;
105     }
106 
getStartTime()107     public String getStartTime() {
108         return mStartTime;
109     }
110 
getEndTime()111     public String getEndTime() {
112         return mEndTime;
113     }
114 
getStackTrace()115     public String getStackTrace() {
116         return mStackTrace;
117     }
118 
setStackTrace(String stackTrace)119     public void setStackTrace(String stackTrace) {
120 
121         mStackTrace = sanitizeStackTrace(stackTrace);
122         mMessage = getFailureMessageFromStackTrace(mStackTrace);
123     }
124 
getReportLog()125     public ReportLog getReportLog() {
126         return mReport;
127     }
128 
setReportLog(ReportLog report)129     public void setReportLog(ReportLog report) {
130         mReport = report;
131     }
132 
updateEndTime()133     public void updateEndTime() {
134         mEndTime = TimeUtil.getTimestamp();
135     }
136 
setResultStatus(CtsTestStatus status)137     public void setResultStatus(CtsTestStatus status) {
138         mResult = status;
139     }
140 
141     /**
142      * Serialize this object and all its contents to XML.
143      *
144      * @param serializer
145      * @throws IOException
146      */
serialize(KXmlSerializer serializer)147     public void serialize(KXmlSerializer serializer)
148             throws IOException {
149         serializer.startTag(CtsXmlResultReporter.ns, TAG);
150         serializer.attribute(CtsXmlResultReporter.ns, NAME_ATTR, getName());
151         serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult.getValue());
152         serializer.attribute(CtsXmlResultReporter.ns, STARTTIME_ATTR, mStartTime);
153         serializer.attribute(CtsXmlResultReporter.ns, ENDTIME_ATTR, mEndTime);
154 
155         serializeTestLogsLocked(serializer);
156 
157         if (mMessage != null) {
158             serializer.startTag(CtsXmlResultReporter.ns, SCENE_TAG);
159             serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, mMessage);
160             if (mStackTrace != null) {
161                 serializer.startTag(CtsXmlResultReporter.ns, STACK_TAG);
162                 serializer.text(mStackTrace);
163                 serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
164             }
165             serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
166         }
167         MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(serializer);
168         metricsXmlSerializer.serialize(mReport);
169         serializer.endTag(CtsXmlResultReporter.ns, TAG);
170     }
171 
172     /**
173      * Strip out any invalid XML characters that might cause the report to be unviewable.
174      * http://www.w3.org/TR/REC-xml/#dt-character
175      */
sanitizeStackTrace(String trace)176     private static String sanitizeStackTrace(String trace) {
177         if (trace != null) {
178             return trace.replaceAll("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
179         } else {
180             return null;
181         }
182     }
183 
184     /**
185      * Gets the failure message to show from the stack trace.
186      * <p/>
187      * Exposed for unit testing
188      *
189      * @param stack the full stack trace
190      * @return the failure message
191      */
getFailureMessageFromStackTrace(String stack)192     static String getFailureMessageFromStackTrace(String stack) {
193         // return the first two lines of stack as failure message
194         int endPoint = stack.indexOf('\n');
195         if (endPoint != -1) {
196             int nextLine = stack.indexOf('\n', endPoint + 1);
197             if (nextLine != -1) {
198                 return stack.substring(0, nextLine);
199             }
200         }
201         return stack;
202     }
203 
204     /**
205      * Populates this class with test result data parsed from XML.
206      *
207      * @param parser the {@link XmlPullParser}. Expected to be pointing at start
208      *            of a Test tag
209      */
210     @Override
parse(XmlPullParser parser)211     void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
212         if (!parser.getName().equals(TAG)) {
213             throw new XmlPullParserException(String.format(
214                     "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
215         }
216         setName(getAttribute(parser, NAME_ATTR));
217         mResult = CtsTestStatus.getStatus(getAttribute(parser, RESULT_ATTR));
218         mStartTime = getAttribute(parser, STARTTIME_ATTR);
219         mEndTime = getAttribute(parser, ENDTIME_ATTR);
220 
221         int eventType = parser.next();
222         while (eventType != XmlPullParser.END_DOCUMENT) {
223             if (eventType == XmlPullParser.START_TAG && parser.getName().equals(SCENE_TAG)) {
224                 mMessage = getAttribute(parser, MESSAGE_ATTR);
225             } else if (eventType == XmlPullParser.START_TAG && parser.getName().equals(STACK_TAG)) {
226                 mStackTrace = parser.nextText();
227             } else if (eventType == XmlPullParser.START_TAG && TestLog.isTag(parser.getName())) {
228                 parseTestLog(parser);
229             } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
230                 return;
231             }
232             eventType = parser.next();
233         }
234     }
235 
236     /** Parse a TestLog entry from the parser positioned at a TestLog tag. */
parseTestLog(XmlPullParser parser)237     private void parseTestLog(XmlPullParser parser) throws XmlPullParserException{
238         TestLog log = TestLog.fromXml(parser);
239         if (log == null) {
240             throw new XmlPullParserException("invalid XML: bad test log tag");
241         }
242         addTestLog(log);
243     }
244 
245     /** Add a TestLog to the test in a thread safe manner. */
addTestLogLocked(TestLog testLog)246     private synchronized void addTestLogLocked(TestLog testLog) {
247         if (mTestLogs == null) {
248             mTestLogs = new ArrayList<>(TestLogType.values().length);
249         }
250         mTestLogs.add(testLog);
251     }
252 
253     /** Serialize the TestLogs of this test in a thread safe manner. */
serializeTestLogsLocked(KXmlSerializer serializer)254     private synchronized void serializeTestLogsLocked(KXmlSerializer serializer) throws IOException {
255         if (mTestLogs != null) {
256             for (TestLog log : mTestLogs) {
257                 log.serialize(serializer);
258             }
259         }
260     }
261 }
262