1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program Test Executor 3 * ------------------------------------------ 4 * 5 * Copyright 2014 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 *//*! 20 * \file 21 * \brief Test log writer. 22 *//*--------------------------------------------------------------------*/ 23 24 #include "xeTestLogWriter.hpp" 25 #include "xeXMLWriter.hpp" 26 #include "deStringUtil.hpp" 27 28 #include <fstream> 29 30 namespace xe 31 { 32 33 static const char* TEST_LOG_VERSION = "0.3.3"; 34 35 /* Batch result writer. */ 36 37 struct ContainerValue 38 { 39 ContainerValue (const std::string& value_) : value(value_) {} 40 ContainerValue (const char* value_) : value(value_) {} 41 std::string value; 42 }; 43 44 std::ostream& operator<< (std::ostream& stream, const ContainerValue& value) 45 { 46 if (value.value.find(' ') != std::string::npos) 47 { 48 // Escape. 49 stream << '"'; 50 for (std::string::const_iterator i = value.value.begin(); i != value.value.end(); i++) 51 { 52 if (*i == '"' || *i == '\\') 53 stream << '\\'; 54 stream << *i; 55 } 56 stream << '"'; 57 } 58 else 59 stream << value.value; 60 61 return stream; 62 } 63 64 static void writeSessionInfo (const SessionInfo& info, std::ostream& stream) 65 { 66 if (!info.releaseName.empty()) 67 stream << "#sessionInfo releaseName " << ContainerValue(info.releaseName) << "\n"; 68 69 if (!info.releaseId.empty()) 70 stream << "#sessionInfo releaseId " << ContainerValue(info.releaseId) << "\n"; 71 72 if (!info.targetName.empty()) 73 stream << "#sessionInfo targetName " << ContainerValue(info.targetName) << "\n"; 74 75 if (!info.candyTargetName.empty()) 76 stream << "#sessionInfo candyTargetName " << ContainerValue(info.candyTargetName) << "\n"; 77 78 if (!info.configName.empty()) 79 stream << "#sessionInfo configName " << ContainerValue(info.configName) << "\n"; 80 81 if (!info.resultName.empty()) 82 stream << "#sessionInfo resultName " << ContainerValue(info.resultName) << "\n"; 83 84 // \note Current format uses unescaped timestamps for some strange reason. 85 if (!info.timestamp.empty()) 86 stream << "#sessionInfo timestamp " << info.timestamp << "\n"; 87 } 88 89 static void writeTestCase (const TestCaseResultData& caseData, std::ostream& stream) 90 { 91 stream << "\n#beginTestCaseResult " << caseData.getTestCasePath() << "\n"; 92 93 if (caseData.getDataSize() > 0) 94 { 95 stream.write((const char*)caseData.getData(), caseData.getDataSize()); 96 97 deUint8 lastCh = caseData.getData()[caseData.getDataSize()-1]; 98 if (lastCh != '\n' && lastCh != '\r') 99 stream << "\n"; 100 } 101 102 TestStatusCode dataCode = caseData.getStatusCode(); 103 if (dataCode == TESTSTATUSCODE_CRASH || 104 dataCode == TESTSTATUSCODE_TIMEOUT || 105 dataCode == TESTSTATUSCODE_TERMINATED) 106 stream << "#terminateTestCaseResult " << getTestStatusCodeName(dataCode) << "\n"; 107 else 108 stream << "#endTestCaseResult\n"; 109 } 110 111 void writeTestLog (const BatchResult& result, std::ostream& stream) 112 { 113 writeSessionInfo(result.getSessionInfo(), stream); 114 115 stream << "#beginSession\n"; 116 117 for (int ndx = 0; ndx < result.getNumTestCaseResults(); ndx++) 118 { 119 ConstTestCaseResultPtr caseData = result.getTestCaseResult(ndx); 120 writeTestCase(*caseData, stream); 121 } 122 123 stream << "\n#endSession\n"; 124 } 125 126 void writeBatchResultToFile (const BatchResult& result, const char* filename) 127 { 128 std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc); 129 writeTestLog(result, str); 130 str.close(); 131 } 132 133 /* Test result log writer. */ 134 135 static const char* getImageFormatName (ri::Image::Format format) 136 { 137 switch (format) 138 { 139 case ri::Image::FORMAT_RGB888: return "RGB888"; 140 case ri::Image::FORMAT_RGBA8888: return "RGBA8888"; 141 default: 142 DE_ASSERT(false); 143 return DE_NULL; 144 } 145 } 146 147 static const char* getImageCompressionName (ri::Image::Compression compression) 148 { 149 switch (compression) 150 { 151 case ri::Image::COMPRESSION_NONE: return "None"; 152 case ri::Image::COMPRESSION_PNG: return "PNG"; 153 default: 154 DE_ASSERT(false); 155 return DE_NULL; 156 } 157 } 158 159 static const char* getSampleValueTagName (ri::ValueInfo::ValueTag tag) 160 { 161 switch (tag) 162 { 163 case ri::ValueInfo::VALUETAG_PREDICTOR: return "Predictor"; 164 case ri::ValueInfo::VALUETAG_RESPONSE: return "Response"; 165 default: 166 DE_ASSERT(false); 167 return DE_NULL; 168 } 169 } 170 171 inline const char* getBoolName (bool val) 172 { 173 return val ? "True" : "False"; 174 } 175 176 // \todo [2012-09-07 pyry] Move to tcutil? 177 class Base64Formatter 178 { 179 public: 180 const deUint8* data; 181 int numBytes; 182 183 Base64Formatter (const deUint8* data_, int numBytes_) : data(data_), numBytes(numBytes_) {} 184 }; 185 186 std::ostream& operator<< (std::ostream& str, const Base64Formatter& fmt) 187 { 188 static const char s_base64Table[64] = 189 { 190 'A','B','C','D','E','F','G','H','I','J','K','L','M', 191 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 192 'a','b','c','d','e','f','g','h','i','j','k','l','m', 193 'n','o','p','q','r','s','t','u','v','w','x','y','z', 194 '0','1','2','3','4','5','6','7','8','9','+','/' 195 }; 196 197 const deUint8* data = fmt.data; 198 int numBytes = fmt.numBytes; 199 int srcNdx = 0; 200 201 DE_ASSERT(data && (numBytes > 0)); 202 203 /* Loop all input chars. */ 204 while (srcNdx < numBytes) 205 { 206 int numRead = de::min(3, numBytes - srcNdx); 207 deUint8 s0 = data[srcNdx]; 208 deUint8 s1 = (numRead >= 2) ? data[srcNdx+1] : 0; 209 deUint8 s2 = (numRead >= 3) ? data[srcNdx+2] : 0; 210 char d[4]; 211 212 srcNdx += numRead; 213 214 d[0] = s_base64Table[s0 >> 2]; 215 d[1] = s_base64Table[((s0&0x3)<<4) | (s1>>4)]; 216 d[2] = s_base64Table[((s1&0xF)<<2) | (s2>>6)]; 217 d[3] = s_base64Table[s2&0x3F]; 218 219 if (numRead < 3) d[3] = '='; 220 if (numRead < 2) d[2] = '='; 221 222 /* Write data. */ 223 str.write(&d[0], sizeof(d)); 224 } 225 226 return str; 227 } 228 229 inline Base64Formatter toBase64 (const deUint8* bytes, int numBytes) { return Base64Formatter(bytes, numBytes); } 230 231 static const char* getStatusName (bool value) 232 { 233 return value ? "OK" : "Fail"; 234 } 235 236 static void writeResultItem (const ri::Item& item, xml::Writer& dst) 237 { 238 using xml::Writer; 239 240 switch (item.getType()) 241 { 242 case ri::TYPE_RESULT: 243 // Ignored here, written at end. 244 break; 245 246 case ri::TYPE_TEXT: 247 dst << Writer::BeginElement("Text") << static_cast<const ri::Text&>(item).text << Writer::EndElement; 248 break; 249 250 case ri::TYPE_NUMBER: 251 { 252 const ri::Number& number = static_cast<const ri::Number&>(item); 253 dst << Writer::BeginElement("Number") 254 << Writer::Attribute("Name", number.name) 255 << Writer::Attribute("Description", number.description) 256 << Writer::Attribute("Unit", number.unit) 257 << Writer::Attribute("Tag", number.tag) 258 << number.value 259 << Writer::EndElement; 260 break; 261 } 262 263 case ri::TYPE_IMAGE: 264 { 265 const ri::Image& image = static_cast<const ri::Image&>(item); 266 dst << Writer::BeginElement("Image") 267 << Writer::Attribute("Name", image.name) 268 << Writer::Attribute("Description", image.description) 269 << Writer::Attribute("Width", de::toString(image.width)) 270 << Writer::Attribute("Height", de::toString(image.height)) 271 << Writer::Attribute("Format", getImageFormatName(image.format)) 272 << Writer::Attribute("CompressionMode", getImageCompressionName(image.compression)) 273 << toBase64(&image.data[0], (int)image.data.size()) 274 << Writer::EndElement; 275 break; 276 } 277 278 case ri::TYPE_IMAGESET: 279 { 280 const ri::ImageSet& imageSet = static_cast<const ri::ImageSet&>(item); 281 dst << Writer::BeginElement("ImageSet") 282 << Writer::Attribute("Name", imageSet.name) 283 << Writer::Attribute("Description", imageSet.description); 284 285 for (int ndx = 0; ndx < imageSet.images.getNumItems(); ndx++) 286 writeResultItem(imageSet.images.getItem(ndx), dst); 287 288 dst << Writer::EndElement; 289 break; 290 } 291 292 case ri::TYPE_SHADER: 293 { 294 const ri::Shader& shader = static_cast<const ri::Shader&>(item); 295 const char* tagName = DE_NULL; 296 297 switch (shader.shaderType) 298 { 299 case ri::Shader::SHADERTYPE_VERTEX: tagName = "VertexShader"; break; 300 case ri::Shader::SHADERTYPE_FRAGMENT: tagName = "FragmentShader"; break; 301 case ri::Shader::SHADERTYPE_GEOMETRY: tagName = "GeometryShader"; break; 302 case ri::Shader::SHADERTYPE_TESS_CONTROL: tagName = "TessControlShader"; break; 303 case ri::Shader::SHADERTYPE_TESS_EVALUATION: tagName = "TessEvaluationShader"; break; 304 case ri::Shader::SHADERTYPE_COMPUTE: tagName = "ComputeShader"; break; 305 default: 306 throw Error("Unknown shader type"); 307 } 308 309 dst << Writer::BeginElement(tagName) 310 << Writer::Attribute("CompileStatus", getStatusName(shader.compileStatus)); 311 312 writeResultItem(shader.source, dst); 313 writeResultItem(shader.infoLog, dst); 314 315 dst << Writer::EndElement; 316 break; 317 } 318 319 case ri::TYPE_SHADERPROGRAM: 320 { 321 const ri::ShaderProgram& program = static_cast<const ri::ShaderProgram&>(item); 322 dst << Writer::BeginElement("ShaderProgram") 323 << Writer::Attribute("LinkStatus", getStatusName(program.linkStatus)); 324 325 writeResultItem(program.linkInfoLog, dst); 326 327 for (int ndx = 0; ndx < program.shaders.getNumItems(); ndx++) 328 writeResultItem(program.shaders.getItem(ndx), dst); 329 330 dst << Writer::EndElement; 331 break; 332 } 333 334 case ri::TYPE_SHADERSOURCE: 335 dst << Writer::BeginElement("ShaderSource") << static_cast<const ri::ShaderSource&>(item).source << Writer::EndElement; 336 break; 337 338 case ri::TYPE_SPIRVSOURCE: 339 dst << Writer::BeginElement("SpirVAssemblySource") << static_cast<const ri::SpirVSource&>(item).source << Writer::EndElement; 340 break; 341 342 case ri::TYPE_INFOLOG: 343 dst << Writer::BeginElement("InfoLog") << static_cast<const ri::InfoLog&>(item).log << Writer::EndElement; 344 break; 345 346 case ri::TYPE_SECTION: 347 { 348 const ri::Section& section = static_cast<const ri::Section&>(item); 349 dst << Writer::BeginElement("Section") 350 << Writer::Attribute("Name", section.name) 351 << Writer::Attribute("Description", section.description); 352 353 for (int ndx = 0; ndx < section.items.getNumItems(); ndx++) 354 writeResultItem(section.items.getItem(ndx), dst); 355 356 dst << Writer::EndElement; 357 break; 358 } 359 360 case ri::TYPE_KERNELSOURCE: 361 dst << Writer::BeginElement("KernelSource") << static_cast<const ri::KernelSource&>(item).source << Writer::EndElement; 362 break; 363 364 case ri::TYPE_COMPILEINFO: 365 { 366 const ri::CompileInfo& compileInfo = static_cast<const ri::CompileInfo&>(item); 367 dst << Writer::BeginElement("CompileInfo") 368 << Writer::Attribute("Name", compileInfo.name) 369 << Writer::Attribute("Description", compileInfo.description) 370 << Writer::Attribute("CompileStatus", getStatusName(compileInfo.compileStatus)); 371 372 writeResultItem(compileInfo.infoLog, dst); 373 374 dst << Writer::EndElement; 375 break; 376 } 377 378 case ri::TYPE_EGLCONFIG: 379 { 380 const ri::EglConfig& config = static_cast<const ri::EglConfig&>(item); 381 dst << Writer::BeginElement("EglConfig") 382 << Writer::Attribute("BufferSize", de::toString(config.bufferSize)) 383 << Writer::Attribute("RedSize", de::toString(config.redSize)) 384 << Writer::Attribute("GreenSize", de::toString(config.greenSize)) 385 << Writer::Attribute("BlueSize", de::toString(config.blueSize)) 386 << Writer::Attribute("LuminanceSize", de::toString(config.luminanceSize)) 387 << Writer::Attribute("AlphaSize", de::toString(config.alphaSize)) 388 << Writer::Attribute("AlphaMaskSize", de::toString(config.alphaMaskSize)) 389 << Writer::Attribute("BindToTextureRGB", getBoolName(config.bindToTextureRGB)) 390 << Writer::Attribute("BindToTextureRGBA", getBoolName(config.bindToTextureRGBA)) 391 << Writer::Attribute("ColorBufferType", config.colorBufferType) 392 << Writer::Attribute("ConfigCaveat", config.configCaveat) 393 << Writer::Attribute("ConfigID", de::toString(config.configID)) 394 << Writer::Attribute("Conformant", config.conformant) 395 << Writer::Attribute("DepthSize", de::toString(config.depthSize)) 396 << Writer::Attribute("Level", de::toString(config.level)) 397 << Writer::Attribute("MaxPBufferWidth", de::toString(config.maxPBufferWidth)) 398 << Writer::Attribute("MaxPBufferHeight", de::toString(config.maxPBufferHeight)) 399 << Writer::Attribute("MaxPBufferPixels", de::toString(config.maxPBufferPixels)) 400 << Writer::Attribute("MaxSwapInterval", de::toString(config.maxSwapInterval)) 401 << Writer::Attribute("MinSwapInterval", de::toString(config.minSwapInterval)) 402 << Writer::Attribute("NativeRenderable", getBoolName(config.nativeRenderable)) 403 << Writer::Attribute("RenderableType", config.renderableType) 404 << Writer::Attribute("SampleBuffers", de::toString(config.sampleBuffers)) 405 << Writer::Attribute("Samples", de::toString(config.samples)) 406 << Writer::Attribute("StencilSize", de::toString(config.stencilSize)) 407 << Writer::Attribute("SurfaceTypes", config.surfaceTypes) 408 << Writer::Attribute("TransparentType", config.transparentType) 409 << Writer::Attribute("TransparentRedValue", de::toString(config.transparentRedValue)) 410 << Writer::Attribute("TransparentGreenValue", de::toString(config.transparentGreenValue)) 411 << Writer::Attribute("TransparentBlueValue", de::toString(config.transparentBlueValue)) 412 << Writer::EndElement; 413 break; 414 } 415 416 case ri::TYPE_EGLCONFIGSET: 417 { 418 const ri::EglConfigSet& configSet = static_cast<const ri::EglConfigSet&>(item); 419 dst << Writer::BeginElement("EglConfigSet") 420 << Writer::Attribute("Name", configSet.name) 421 << Writer::Attribute("Description", configSet.description); 422 423 for (int ndx = 0; ndx < configSet.configs.getNumItems(); ndx++) 424 writeResultItem(configSet.configs.getItem(ndx), dst); 425 426 dst << Writer::EndElement; 427 break; 428 } 429 430 case ri::TYPE_SAMPLELIST: 431 { 432 const ri::SampleList& list = static_cast<const ri::SampleList&>(item); 433 dst << Writer::BeginElement("SampleList") 434 << Writer::Attribute("Name", list.name) 435 << Writer::Attribute("Description", list.description); 436 437 writeResultItem(list.sampleInfo, dst); 438 439 for (int ndx = 0; ndx < list.samples.getNumItems(); ndx++) 440 writeResultItem(list.samples.getItem(ndx), dst); 441 442 dst << Writer::EndElement; 443 break; 444 } 445 446 case ri::TYPE_SAMPLEINFO: 447 { 448 const ri::SampleInfo& info = static_cast<const ri::SampleInfo&>(item); 449 dst << Writer::BeginElement("SampleInfo"); 450 for (int ndx = 0; ndx < info.valueInfos.getNumItems(); ndx++) 451 writeResultItem(info.valueInfos.getItem(ndx), dst); 452 dst << Writer::EndElement; 453 break; 454 } 455 456 case ri::TYPE_VALUEINFO: 457 { 458 const ri::ValueInfo& info = static_cast<const ri::ValueInfo&>(item); 459 dst << Writer::BeginElement("ValueInfo") 460 << Writer::Attribute("Name", info.name) 461 << Writer::Attribute("Description", info.description) 462 << Writer::Attribute("Tag", getSampleValueTagName(info.tag)); 463 if (!info.unit.empty()) 464 dst << Writer::Attribute("Unit", info.unit); 465 dst << Writer::EndElement; 466 break; 467 } 468 469 case ri::TYPE_SAMPLE: 470 { 471 const ri::Sample& sample = static_cast<const ri::Sample&>(item); 472 dst << Writer::BeginElement("Sample"); 473 for (int ndx = 0; ndx < sample.values.getNumItems(); ndx++) 474 writeResultItem(sample.values.getItem(ndx), dst); 475 dst << Writer::EndElement; 476 break; 477 } 478 479 case ri::TYPE_SAMPLEVALUE: 480 { 481 const ri::SampleValue& value = static_cast<const ri::SampleValue&>(item); 482 dst << Writer::BeginElement("Value") 483 << value.value 484 << Writer::EndElement; 485 break; 486 } 487 488 default: 489 XE_FAIL("Unsupported result item"); 490 } 491 } 492 493 void writeTestResult (const TestCaseResult& result, xe::xml::Writer& xmlWriter) 494 { 495 using xml::Writer; 496 497 xmlWriter << Writer::BeginElement("TestCaseResult") 498 << Writer::Attribute("Version", TEST_LOG_VERSION) 499 << Writer::Attribute("CasePath", result.casePath) 500 << Writer::Attribute("CaseType", getTestCaseTypeName(result.caseType)); 501 502 for (int ndx = 0; ndx < result.resultItems.getNumItems(); ndx++) 503 writeResultItem(result.resultItems.getItem(ndx), xmlWriter); 504 505 // Result item is not logged until end. 506 xmlWriter << Writer::BeginElement("Result") 507 << Writer::Attribute("StatusCode", getTestStatusCodeName(result.statusCode)) 508 << result.statusDetails 509 << Writer::EndElement; 510 511 xmlWriter << Writer::EndElement; 512 } 513 514 void writeTestResult (const TestCaseResult& result, std::ostream& stream) 515 { 516 xml::Writer xmlWriter(stream); 517 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 518 writeTestResult(result, xmlWriter); 519 } 520 521 void writeTestResultToFile (const TestCaseResult& result, const char* filename) 522 { 523 std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc); 524 writeTestResult(result, str); 525 str.close(); 526 } 527 528 } // xe 529