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 Batch result to XML export.
22  *//*--------------------------------------------------------------------*/
23 
24 #include "xeTestLogParser.hpp"
25 #include "xeTestResultParser.hpp"
26 #include "xeXMLWriter.hpp"
27 #include "xeTestLogWriter.hpp"
28 #include "deFilePath.hpp"
29 #include "deString.h"
30 #include "deStringUtil.hpp"
31 #include "deCommandLine.hpp"
32 
33 #include <vector>
34 #include <string>
35 #include <map>
36 #include <cstdio>
37 #include <fstream>
38 #include <iostream>
39 
40 using std::vector;
41 using std::string;
42 using std::map;
43 
44 static const char*	CASELIST_STYLESHEET		= "caselist.xsl";
45 static const char*	TESTCASE_STYLESHEET		= "testlog.xsl";
46 
47 enum OutputMode
48 {
49 	OUTPUTMODE_SEPARATE = 0,	//!< Separate
50 	OUTPUTMODE_SINGLE,
51 
52 	OUTPUTMODE_LAST
53 };
54 
55 namespace opt
56 {
57 
58 DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode);
59 
registerOptions(de::cmdline::Parser & parser)60 void registerOptions (de::cmdline::Parser& parser)
61 {
62 	using de::cmdline::Option;
63 	using de::cmdline::NamedValue;
64 
65 	static const NamedValue<OutputMode> s_modes[] =
66 	{
67 		{ "single",		OUTPUTMODE_SINGLE	},
68 		{ "separate",	OUTPUTMODE_SEPARATE	}
69 	};
70 
71 	parser << Option<OutMode>("m", "mode", "Output mode", s_modes, "single");
72 }
73 
74 } // opt
75 
76 struct CommandLine
77 {
CommandLineCommandLine78 	CommandLine (void)
79 		: outputMode(OUTPUTMODE_SINGLE)
80 	{
81 	}
82 
83 	std::string		batchResultFile;
84 	std::string		outputPath;
85 	OutputMode		outputMode;
86 };
87 
parseCommandLine(CommandLine & cmdLine,int argc,const char * const * argv)88 static bool parseCommandLine (CommandLine& cmdLine, int argc, const char* const* argv)
89 {
90 	de::cmdline::Parser			parser;
91 	de::cmdline::CommandLine	opts;
92 
93 	opt::registerOptions(parser);
94 
95 	if (!parser.parse(argc-1, argv+1, &opts, std::cerr) ||
96 		opts.getArgs().size() != 2)
97 	{
98 		printf("%s: [options] [testlog] [destination path]\n", argv[0]);
99 		parser.help(std::cout);
100 		return false;
101 	}
102 
103 	cmdLine.outputMode		= opts.getOption<opt::OutMode>();
104 	cmdLine.batchResultFile	= opts.getArgs()[0];
105 	cmdLine.outputPath		= opts.getArgs()[1];
106 
107 	return true;
108 }
109 
parseBatchResult(xe::TestLogParser & parser,const char * filename)110 static void parseBatchResult (xe::TestLogParser& parser, const char* filename)
111 {
112 	std::ifstream	in			(filename, std::ios_base::binary);
113 	deUint8			buf[2048];
114 
115 	for (;;)
116 	{
117 		in.read((char*)&buf[0], sizeof(buf));
118 		int numRead = (int)in.gcount();
119 
120 		if (numRead > 0)
121 			parser.parse(&buf[0], numRead);
122 
123 		if (numRead < (int)sizeof(buf))
124 			break;
125 	}
126 }
127 
128 // Export to single file
129 
130 struct BatchResultTotals
131 {
BatchResultTotalsBatchResultTotals132 	BatchResultTotals (void)
133 	{
134 		for (int i = 0;i < xe::TESTSTATUSCODE_LAST; i++)
135 			countByCode[i] = 0;
136 	}
137 
138 	int countByCode[xe::TESTSTATUSCODE_LAST];
139 };
140 
141 class ResultToSingleXmlLogHandler : public xe::TestLogHandler
142 {
143 public:
ResultToSingleXmlLogHandler(xe::xml::Writer & writer,BatchResultTotals & totals)144 	ResultToSingleXmlLogHandler (xe::xml::Writer& writer, BatchResultTotals& totals)
145 		: m_writer	(writer)
146 		, m_totals	(totals)
147 	{
148 	}
149 
setSessionInfo(const xe::SessionInfo &)150 	void setSessionInfo (const xe::SessionInfo&)
151 	{
152 	}
153 
startTestCaseResult(const char * casePath)154 	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
155 	{
156 		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
157 	}
158 
testCaseResultUpdated(const xe::TestCaseResultPtr &)159 	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
160 	{
161 	}
162 
testCaseResultComplete(const xe::TestCaseResultPtr & resultData)163 	void testCaseResultComplete (const xe::TestCaseResultPtr& resultData)
164 	{
165 		xe::TestCaseResult result;
166 
167 		xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());
168 
169 		// Write result.
170 		xe::writeTestResult(result, m_writer);
171 
172 		// Record total
173 		XE_CHECK(de::inBounds<int>(result.statusCode, 0, xe::TESTSTATUSCODE_LAST));
174 		m_totals.countByCode[result.statusCode] += 1;
175 	}
176 
177 private:
178 	xe::xml::Writer&		m_writer;
179 	BatchResultTotals&		m_totals;
180 	xe::TestResultParser	m_resultParser;
181 };
182 
writeTotals(xe::xml::Writer & writer,const BatchResultTotals & totals)183 static void writeTotals (xe::xml::Writer& writer, const BatchResultTotals& totals)
184 {
185 	using xe::xml::Writer;
186 
187 	int totalCases = 0;
188 
189 	writer << Writer::BeginElement("ResultTotals");
190 
191 	for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++)
192 	{
193 		writer << Writer::Attribute(xe::getTestStatusCodeName((xe::TestStatusCode)code), de::toString(totals.countByCode[code]).c_str());
194 		totalCases += totals.countByCode[code];
195 	}
196 
197 	writer << Writer::Attribute("All", de::toString(totalCases).c_str())
198 		   << Writer::EndElement;
199 }
200 
batchResultToSingleXmlFile(const char * batchResultFilename,const char * dstFileName)201 static void batchResultToSingleXmlFile (const char* batchResultFilename, const char* dstFileName)
202 {
203 	std::ofstream				out			(dstFileName, std::ios_base::binary);
204 	xe::xml::Writer				writer		(out);
205 	BatchResultTotals			totals;
206 	ResultToSingleXmlLogHandler	handler		(writer, totals);
207 	xe::TestLogParser			parser		(&handler);
208 
209 	XE_CHECK(out.good());
210 
211 	out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
212 		<< "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";
213 
214 	writer << xe::xml::Writer::BeginElement("BatchResult")
215 		   << xe::xml::Writer::Attribute("FileName", de::FilePath(batchResultFilename).getBaseName());
216 
217 	// Parse and write individual cases
218 	parseBatchResult(parser, batchResultFilename);
219 
220 	// Write ResultTotals
221 	writeTotals(writer, totals);
222 
223 	writer << xe::xml::Writer::EndElement;
224 	out << "\n";
225 }
226 
227 // Export to separate files
228 
229 class ResultToXmlFilesLogHandler : public xe::TestLogHandler
230 {
231 public:
ResultToXmlFilesLogHandler(vector<xe::TestCaseResultHeader> & resultHeaders,const char * dstPath)232 	ResultToXmlFilesLogHandler (vector<xe::TestCaseResultHeader>& resultHeaders, const char* dstPath)
233 		: m_resultHeaders	(resultHeaders)
234 		, m_dstPath			(dstPath)
235 	{
236 	}
237 
setSessionInfo(const xe::SessionInfo &)238 	void setSessionInfo (const xe::SessionInfo&)
239 	{
240 	}
241 
startTestCaseResult(const char * casePath)242 	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
243 	{
244 		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
245 	}
246 
testCaseResultUpdated(const xe::TestCaseResultPtr &)247 	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
248 	{
249 	}
250 
testCaseResultComplete(const xe::TestCaseResultPtr & resultData)251 	void testCaseResultComplete (const xe::TestCaseResultPtr& resultData)
252 	{
253 		xe::TestCaseResult result;
254 
255 		xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());
256 
257 		// Write result.
258 		{
259 			de::FilePath	casePath	= de::FilePath::join(m_dstPath, (result.casePath + ".xml").c_str());
260 			std::ofstream	out			(casePath.getPath(), std::ofstream::binary|std::ofstream::trunc);
261 			xe::xml::Writer	xmlWriter	(out);
262 
263 			if (!out.good())
264 				throw xe::Error(string("Failed to open ") + casePath.getPath());
265 
266 			out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
267 				<< "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";
268 			xe::writeTestResult(result, xmlWriter);
269 			out << "\n";
270 		}
271 
272 		m_resultHeaders.push_back(xe::TestCaseResultHeader(result));
273 	}
274 
275 private:
276 	vector<xe::TestCaseResultHeader>&	m_resultHeaders;
277 	std::string							m_dstPath;
278 	xe::TestResultParser				m_resultParser;
279 };
280 
281 typedef std::map<const xe::TestCase*, const xe::TestCaseResultHeader*> ShortTestResultMap;
282 
writeTestCaseListNode(const xe::TestNode * testNode,const ShortTestResultMap & resultMap,xe::xml::Writer & dst)283 static void writeTestCaseListNode (const xe::TestNode* testNode, const ShortTestResultMap& resultMap, xe::xml::Writer& dst)
284 {
285 	using xe::xml::Writer;
286 
287 	bool	isGroup		= testNode->getNodeType() == xe::TESTNODETYPE_GROUP;
288 	string	fullPath;
289 	testNode->getFullPath(fullPath);
290 
291 	if (isGroup)
292 	{
293 		const xe::TestGroup* group = static_cast<const xe::TestGroup*>(testNode);
294 
295 		dst << Writer::BeginElement("TestGroup")
296 			<< Writer::Attribute("Name", testNode->getName());
297 
298 		for (int childNdx = 0; childNdx < group->getNumChildren(); childNdx++)
299 			writeTestCaseListNode(group->getChild(childNdx), resultMap, dst);
300 
301 		dst << Writer::EndElement;
302 	}
303 	else
304 	{
305 		DE_ASSERT(testNode->getNodeType() == xe::TESTNODETYPE_TEST_CASE);
306 
307 		const xe::TestCase*					testCase	= static_cast<const xe::TestCase*>(testNode);
308 		ShortTestResultMap::const_iterator	resultPos	= resultMap.find(testCase);
309 		const xe::TestCaseResultHeader*		result		= resultPos != resultMap.end() ? resultPos->second : DE_NULL;
310 
311 		DE_ASSERT(result);
312 
313 		dst << Writer::BeginElement("TestCase")
314 			<< Writer::Attribute("Name", testNode->getName())
315 			<< Writer::Attribute("Type", xe::getTestCaseTypeName(result->caseType))
316 			<< Writer::Attribute("StatusCode", xe::getTestStatusCodeName(result->statusCode))
317 			<< Writer::Attribute("StatusDetails", result->statusDetails.c_str())
318 			<< Writer::EndElement;
319 	}
320 }
321 
writeTestCaseList(const xe::TestRoot & root,const ShortTestResultMap & resultMap,xe::xml::Writer & dst)322 static void writeTestCaseList (const xe::TestRoot& root, const ShortTestResultMap& resultMap, xe::xml::Writer& dst)
323 {
324 	using xe::xml::Writer;
325 
326 	dst << Writer::BeginElement("TestRoot");
327 
328 	for (int childNdx = 0; childNdx < root.getNumChildren(); childNdx++)
329 		writeTestCaseListNode(root.getChild(childNdx), resultMap, dst);
330 
331 	dst << Writer::EndElement;
332 }
333 
batchResultToSeparateXmlFiles(const char * batchResultFilename,const char * dstPath)334 static void batchResultToSeparateXmlFiles (const char* batchResultFilename, const char* dstPath)
335 {
336 	xe::TestRoot						testRoot;
337 	vector<xe::TestCaseResultHeader>	shortResults;
338 	ShortTestResultMap					resultMap;
339 
340 	// Initialize destination directory.
341 	if (!de::FilePath(dstPath).exists())
342 		de::createDirectoryAndParents(dstPath);
343 	else
344 		XE_CHECK_MSG(de::FilePath(dstPath).getType() == de::FilePath::TYPE_DIRECTORY, "Destination is not directory");
345 
346 	// Parse batch result and write out test cases.
347 	{
348 		ResultToXmlFilesLogHandler	handler		(shortResults, dstPath);
349 		xe::TestLogParser			parser		(&handler);
350 
351 		parseBatchResult(parser, batchResultFilename);
352 	}
353 
354 	// Build case hierarchy & short result map.
355 	{
356 		xe::TestHierarchyBuilder hierarchyBuilder(&testRoot);
357 
358 		for (vector<xe::TestCaseResultHeader>::const_iterator result = shortResults.begin(); result != shortResults.end(); result++)
359 		{
360 			xe::TestCase* testCase = hierarchyBuilder.createCase(result->casePath.c_str(), result->caseType);
361 			resultMap.insert(std::make_pair(testCase, &(*result)));
362 		}
363 	}
364 
365 	// Create caselist.
366 	{
367 		de::FilePath	indexPath	= de::FilePath::join(dstPath, "caselist.xml");
368 		std::ofstream	out			(indexPath.getPath(), std::ofstream::binary|std::ofstream::trunc);
369 		xe::xml::Writer	xmlWriter	(out);
370 
371 		XE_CHECK_MSG(out.good(), "Failed to open caselist.xml");
372 
373 		out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
374 			<< "<?xml-stylesheet href=\"" << CASELIST_STYLESHEET << "\" type=\"text/xsl\"?>\n";
375 		writeTestCaseList(testRoot, resultMap, xmlWriter);
376 		out << "\n";
377 	}
378 }
379 
main(int argc,const char * const * argv)380 int main (int argc, const char* const* argv)
381 {
382 	try
383 	{
384 		CommandLine cmdLine;
385 		if (!parseCommandLine(cmdLine, argc, argv))
386 			return -1;
387 
388 		if (cmdLine.outputMode == OUTPUTMODE_SINGLE)
389 			batchResultToSingleXmlFile(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
390 		else
391 			batchResultToSeparateXmlFiles(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
392 	}
393 	catch (const std::exception& e)
394 	{
395 		printf("%s\n", e.what());
396 		return -1;
397 	}
398 
399 	return 0;
400 }
401