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