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