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