blob: dcb3ee6169fd8fa14c94599222ebff7562c166e7 [file] [log] [blame]
Jarkko Poyry3c827362014-09-02 11:48:52 +03001/*-------------------------------------------------------------------------
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
30namespace xe
31{
32
33static const char* TEST_LOG_VERSION = "0.3.3";
34
35/* Batch result writer. */
36
37struct ContainerValue
38{
39 ContainerValue (const std::string& value_) : value(value_) {}
40 ContainerValue (const char* value_) : value(value_) {}
41 std::string value;
42};
43
44std::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
64static 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 stupid reason.
85 if (!info.timestamp.empty())
86 stream << "#sessionInfo timestamp " << info.timestamp << "\n";
87}
88
89static 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
111void 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
126void 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
135static 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
147static 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
159static 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
171inline const char* getBoolName (bool val)
172{
173 return val ? "True" : "False";
174}
175
176// \todo [2012-09-07 pyry] Move to tcutil?
177class Base64Formatter
178{
179public:
180 const deUint8* data;
181 int numBytes;
182
183 Base64Formatter (const deUint8* data_, int numBytes_) : data(data_), numBytes(numBytes_) {}
184};
185
186std::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
229inline Base64Formatter toBase64 (const deUint8* bytes, int numBytes) { return Base64Formatter(bytes, numBytes); }
230
231static const char* getStatusName (bool value)
232{
233 return value ? "OK" : "Fail";
234}
235
236static 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_INFOLOG:
339 dst << Writer::BeginElement("InfoLog") << static_cast<const ri::InfoLog&>(item).log << Writer::EndElement;
340 break;
341
342 case ri::TYPE_SECTION:
343 {
344 const ri::Section& section = static_cast<const ri::Section&>(item);
345 dst << Writer::BeginElement("Section")
346 << Writer::Attribute("Name", section.name)
347 << Writer::Attribute("Description", section.description);
348
349 for (int ndx = 0; ndx < section.items.getNumItems(); ndx++)
350 writeResultItem(section.items.getItem(ndx), dst);
351
352 dst << Writer::EndElement;
353 break;
354 }
355
356 case ri::TYPE_KERNELSOURCE:
357 dst << Writer::BeginElement("KernelSource") << static_cast<const ri::KernelSource&>(item).source << Writer::EndElement;
358 break;
359
360 case ri::TYPE_COMPILEINFO:
361 {
362 const ri::CompileInfo& compileInfo = static_cast<const ri::CompileInfo&>(item);
363 dst << Writer::BeginElement("CompileInfo")
364 << Writer::Attribute("Name", compileInfo.name)
365 << Writer::Attribute("Description", compileInfo.description)
366 << Writer::Attribute("CompileStatus", getStatusName(compileInfo.compileStatus));
367
368 writeResultItem(compileInfo.infoLog, dst);
369
370 dst << Writer::EndElement;
371 break;
372 }
373
374 case ri::TYPE_EGLCONFIG:
375 {
376 const ri::EglConfig& config = static_cast<const ri::EglConfig&>(item);
377 dst << Writer::BeginElement("EglConfig")
378 << Writer::Attribute("BufferSize", de::toString(config.bufferSize))
379 << Writer::Attribute("RedSize", de::toString(config.redSize))
380 << Writer::Attribute("GreenSize", de::toString(config.greenSize))
381 << Writer::Attribute("BlueSize", de::toString(config.blueSize))
382 << Writer::Attribute("LuminanceSize", de::toString(config.luminanceSize))
383 << Writer::Attribute("AlphaSize", de::toString(config.alphaSize))
384 << Writer::Attribute("AlphaMaskSize", de::toString(config.alphaMaskSize))
385 << Writer::Attribute("BindToTextureRGB", getBoolName(config.bindToTextureRGB))
386 << Writer::Attribute("BindToTextureRGBA", getBoolName(config.bindToTextureRGBA))
387 << Writer::Attribute("ColorBufferType", config.colorBufferType)
388 << Writer::Attribute("ConfigCaveat", config.configCaveat)
389 << Writer::Attribute("ConfigID", de::toString(config.configID))
390 << Writer::Attribute("Conformant", config.conformant)
391 << Writer::Attribute("DepthSize", de::toString(config.depthSize))
392 << Writer::Attribute("Level", de::toString(config.level))
393 << Writer::Attribute("MaxPBufferWidth", de::toString(config.maxPBufferWidth))
394 << Writer::Attribute("MaxPBufferHeight", de::toString(config.maxPBufferHeight))
395 << Writer::Attribute("MaxPBufferPixels", de::toString(config.maxPBufferPixels))
396 << Writer::Attribute("MaxSwapInterval", de::toString(config.maxSwapInterval))
397 << Writer::Attribute("MinSwapInterval", de::toString(config.minSwapInterval))
398 << Writer::Attribute("NativeRenderable", getBoolName(config.nativeRenderable))
399 << Writer::Attribute("RenderableType", config.renderableType)
400 << Writer::Attribute("SampleBuffers", de::toString(config.sampleBuffers))
401 << Writer::Attribute("Samples", de::toString(config.samples))
402 << Writer::Attribute("StencilSize", de::toString(config.stencilSize))
403 << Writer::Attribute("SurfaceTypes", config.surfaceTypes)
404 << Writer::Attribute("TransparentType", config.transparentType)
405 << Writer::Attribute("TransparentRedValue", de::toString(config.transparentRedValue))
406 << Writer::Attribute("TransparentGreenValue", de::toString(config.transparentGreenValue))
407 << Writer::Attribute("TransparentBlueValue", de::toString(config.transparentBlueValue))
408 << Writer::EndElement;
409 break;
410 }
411
412 case ri::TYPE_EGLCONFIGSET:
413 {
414 const ri::EglConfigSet& configSet = static_cast<const ri::EglConfigSet&>(item);
415 dst << Writer::BeginElement("EglConfigSet")
416 << Writer::Attribute("Name", configSet.name)
417 << Writer::Attribute("Description", configSet.description);
418
419 for (int ndx = 0; ndx < configSet.configs.getNumItems(); ndx++)
420 writeResultItem(configSet.configs.getItem(ndx), dst);
421
422 dst << Writer::EndElement;
423 break;
424 }
425
426 case ri::TYPE_SAMPLELIST:
427 {
428 const ri::SampleList& list = static_cast<const ri::SampleList&>(item);
429 dst << Writer::BeginElement("SampleList")
430 << Writer::Attribute("Name", list.name)
431 << Writer::Attribute("Description", list.description);
432
433 writeResultItem(list.sampleInfo, dst);
434
435 for (int ndx = 0; ndx < list.samples.getNumItems(); ndx++)
436 writeResultItem(list.samples.getItem(ndx), dst);
437
438 dst << Writer::EndElement;
439 break;
440 }
441
442 case ri::TYPE_SAMPLEINFO:
443 {
444 const ri::SampleInfo& info = static_cast<const ri::SampleInfo&>(item);
445 dst << Writer::BeginElement("SampleInfo");
446 for (int ndx = 0; ndx < info.valueInfos.getNumItems(); ndx++)
447 writeResultItem(info.valueInfos.getItem(ndx), dst);
448 dst << Writer::EndElement;
449 break;
450 }
451
452 case ri::TYPE_VALUEINFO:
453 {
454 const ri::ValueInfo& info = static_cast<const ri::ValueInfo&>(item);
455 dst << Writer::BeginElement("ValueInfo")
456 << Writer::Attribute("Name", info.name)
457 << Writer::Attribute("Description", info.description)
458 << Writer::Attribute("Tag", getSampleValueTagName(info.tag));
459 if (!info.unit.empty())
460 dst << Writer::Attribute("Unit", info.unit);
461 dst << Writer::EndElement;
462 break;
463 }
464
465 case ri::TYPE_SAMPLE:
466 {
467 const ri::Sample& sample = static_cast<const ri::Sample&>(item);
468 dst << Writer::BeginElement("Sample");
469 for (int ndx = 0; ndx < sample.values.getNumItems(); ndx++)
470 writeResultItem(sample.values.getItem(ndx), dst);
471 dst << Writer::EndElement;
472 break;
473 }
474
475 case ri::TYPE_SAMPLEVALUE:
476 {
477 const ri::SampleValue& value = static_cast<const ri::SampleValue&>(item);
478 dst << Writer::BeginElement("Value")
479 << value.value
480 << Writer::EndElement;
481 break;
482 }
483
484 default:
485 XE_FAIL("Unsupported result item");
486 }
487}
488
489void writeTestResult (const TestCaseResult& result, xe::xml::Writer& xmlWriter)
490{
491 using xml::Writer;
492
493 xmlWriter << Writer::BeginElement("TestCaseResult")
494 << Writer::Attribute("Version", TEST_LOG_VERSION)
495 << Writer::Attribute("CasePath", result.casePath)
496 << Writer::Attribute("CaseType", getTestCaseTypeName(result.caseType));
497
498 for (int ndx = 0; ndx < result.resultItems.getNumItems(); ndx++)
499 writeResultItem(result.resultItems.getItem(ndx), xmlWriter);
500
501 // Result item is not logged until end.
502 xmlWriter << Writer::BeginElement("Result")
503 << Writer::Attribute("StatusCode", getTestStatusCodeName(result.statusCode))
504 << result.statusDetails
505 << Writer::EndElement;
506
507 xmlWriter << Writer::EndElement;
508}
509
510void writeTestResult (const TestCaseResult& result, std::ostream& stream)
511{
512 xml::Writer xmlWriter(stream);
513 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
514 writeTestResult(result, xmlWriter);
515}
516
517void writeTestResultToFile (const TestCaseResult& result, const char* filename)
518{
519 std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc);
520 writeTestResult(result, str);
521 str.close();
522}
523
524} // xe