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
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
525