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