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
40using std::vector;
41using std::string;
42using std::map;
43
44static const char*	CASELIST_STYLESHEET		= "caselist.xsl";
45static const char*	TESTCASE_STYLESHEET		= "testlog.xsl";
46
47enum OutputMode
48{
49	OUTPUTMODE_SEPARATE = 0,	//!< Separate
50	OUTPUTMODE_SINGLE,
51
52	OUTPUTMODE_LAST
53};
54
55namespace opt
56{
57
58DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode);
59
60void 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
76struct CommandLine
77{
78	CommandLine (void)
79		: outputMode(OUTPUTMODE_SINGLE)
80	{
81	}
82
83	std::string		batchResultFile;
84	std::string		outputPath;
85	OutputMode		outputMode;
86};
87
88static 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
110static 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
130struct BatchResultTotals
131{
132	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
141class ResultToSingleXmlLogHandler : public xe::TestLogHandler
142{
143public:
144	ResultToSingleXmlLogHandler (xe::xml::Writer& writer, BatchResultTotals& totals)
145		: m_writer	(writer)
146		, m_totals	(totals)
147	{
148	}
149
150	void setSessionInfo (const xe::SessionInfo&)
151	{
152	}
153
154	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
155	{
156		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
157	}
158
159	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
160	{
161	}
162
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
177private:
178	xe::xml::Writer&		m_writer;
179	BatchResultTotals&		m_totals;
180	xe::TestResultParser	m_resultParser;
181};
182
183static 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
201static 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
229class ResultToXmlFilesLogHandler : public xe::TestLogHandler
230{
231public:
232	ResultToXmlFilesLogHandler (vector<xe::TestCaseResultHeader>& resultHeaders, const char* dstPath)
233		: m_resultHeaders	(resultHeaders)
234		, m_dstPath			(dstPath)
235	{
236	}
237
238	void setSessionInfo (const xe::SessionInfo&)
239	{
240	}
241
242	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
243	{
244		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
245	}
246
247	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
248	{
249	}
250
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
275private:
276	vector<xe::TestCaseResultHeader>&	m_resultHeaders;
277	std::string							m_dstPath;
278	xe::TestResultParser				m_resultParser;
279};
280
281typedef std::map<const xe::TestCase*, const xe::TestCaseResultHeader*> ShortTestResultMap;
282
283static 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
322static 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
334static 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
380int 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