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 {
ContainerValuexe::ContainerValue39 	ContainerValue (const std::string& value_)	: value(value_) {}
ContainerValuexe::ContainerValue40 	ContainerValue (const char* value_)			: value(value_) {}
41 	std::string value;
42 };
43 
operator <<(std::ostream & stream,const ContainerValue & value)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 
writeSessionInfo(const SessionInfo & info,std::ostream & stream)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 stupid reason.
85 	if (!info.timestamp.empty())
86 		stream << "#sessionInfo timestamp " << info.timestamp << "\n";
87 }
88 
writeTestCase(const TestCaseResultData & caseData,std::ostream & stream)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 
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:		return "RGB888";
140 		case ri::Image::FORMAT_RGBA8888:	return "RGBA8888";
141 		default:
142 			DE_ASSERT(false);
143 			return DE_NULL;
144 	}
145 }
146 
getImageCompressionName(ri::Image::Compression compression)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 
getSampleValueTagName(ri::ValueInfo::ValueTag tag)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 
getBoolName(bool val)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 
Base64Formatter(const deUint8 * data_,int numBytes_)183 	Base64Formatter (const deUint8* data_, int numBytes_) : data(data_), numBytes(numBytes_) {}
184 };
185 
operator <<(std::ostream & str,const Base64Formatter & fmt)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 
toBase64(const deUint8 * bytes,int numBytes)229 inline Base64Formatter toBase64 (const deUint8* bytes, int numBytes) { return Base64Formatter(bytes, numBytes); }
230 
getStatusName(bool value)231 static const char* getStatusName (bool value)
232 {
233 	return value ? "OK" : "Fail";
234 }
235 
writeResultItem(const ri::Item & item,xml::Writer & dst)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 
writeTestResult(const TestCaseResult & result,xe::xml::Writer & xmlWriter)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 
writeTestResult(const TestCaseResult & result,std::ostream & stream)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 
writeTestResultToFile(const TestCaseResult & result,const char * filename)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