Home | History | Annotate | Download | only in tools
      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 
     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 {
     78 	CommandLine (void)
     79 		: outputMode(OUTPUTMODE_SINGLE)
     80 	{
     81 	}
     82 
     83 	std::string		batchResultFile;
     84 	std::string		outputPath;
     85 	OutputMode		outputMode;
     86 };
     87 
     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 
    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 {
    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 
    141 class ResultToSingleXmlLogHandler : public xe::TestLogHandler
    142 {
    143 public:
    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 
    177 private:
    178 	xe::xml::Writer&		m_writer;
    179 	BatchResultTotals&		m_totals;
    180 	xe::TestResultParser	m_resultParser;
    181 };
    182 
    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 
    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:
    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 
    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 
    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 
    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 
    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 
    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