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 Command line test executor.
     22  *//*--------------------------------------------------------------------*/
     23 
     24 #include "xeBatchExecutor.hpp"
     25 #include "xeLocalTcpIpLink.hpp"
     26 #include "xeTcpIpLink.hpp"
     27 #include "xeTestCaseListParser.hpp"
     28 #include "xeTestLogWriter.hpp"
     29 #include "xeTestResultParser.hpp"
     30 
     31 #include "deCommandLine.hpp"
     32 #include "deDirectoryIterator.hpp"
     33 #include "deStringUtil.hpp"
     34 #include "deUniquePtr.hpp"
     35 
     36 #include "deString.h"
     37 
     38 #include <algorithm>
     39 #include <cstdio>
     40 #include <cstdlib>
     41 #include <fstream>
     42 #include <iostream>
     43 #include <memory>
     44 #include <sstream>
     45 #include <string>
     46 #include <vector>
     47 
     48 #if (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_WIN32)
     49 #	include <signal.h>
     50 #endif
     51 
     52 using std::vector;
     53 using std::string;
     54 
     55 namespace
     56 {
     57 
     58 // Command line arguments.
     59 namespace opt
     60 {
     61 
     62 DE_DECLARE_COMMAND_LINE_OPT(StartServer,	string);
     63 DE_DECLARE_COMMAND_LINE_OPT(Host,			string);
     64 DE_DECLARE_COMMAND_LINE_OPT(Port,			int);
     65 DE_DECLARE_COMMAND_LINE_OPT(CaseListDir,	string);
     66 DE_DECLARE_COMMAND_LINE_OPT(TestSet,		vector<string>);
     67 DE_DECLARE_COMMAND_LINE_OPT(ExcludeSet,		vector<string>);
     68 DE_DECLARE_COMMAND_LINE_OPT(ContinueFile,	string);
     69 DE_DECLARE_COMMAND_LINE_OPT(TestLogFile,	string);
     70 DE_DECLARE_COMMAND_LINE_OPT(InfoLogFile,	string);
     71 DE_DECLARE_COMMAND_LINE_OPT(Summary,		bool);
     72 
     73 // TargetConfiguration
     74 DE_DECLARE_COMMAND_LINE_OPT(BinaryName,		string);
     75 DE_DECLARE_COMMAND_LINE_OPT(WorkingDir,		string);
     76 DE_DECLARE_COMMAND_LINE_OPT(CmdLineArgs,	string);
     77 
     78 void parseCommaSeparatedList (const char* src, vector<string>* dst)
     79 {
     80 	std::istringstream	inStr	(src);
     81 	string			comp;
     82 
     83 	while (std::getline(inStr, comp, ','))
     84 		dst->push_back(comp);
     85 }
     86 
     87 void registerOptions (de::cmdline::Parser& parser)
     88 {
     89 	using de::cmdline::Option;
     90 	using de::cmdline::NamedValue;
     91 
     92 	static const NamedValue<bool> s_yesNo[] =
     93 	{
     94 		{ "yes",	true	},
     95 		{ "no",		false	}
     96 	};
     97 
     98 	parser << Option<StartServer>	("s",		"start-server",	"Start local execserver. Path to the execserver binary.")
     99 		   << Option<Host>			("c",		"connect",		"Connect to host. Address of the execserver.")
    100 		   << Option<Port>			("p",		"port",			"TCP port of the execserver.",											"50016")
    101 		   << Option<CaseListDir>	("cd",		"caselistdir",	"Path to the directory containing test case XML files.",				".")
    102 		   << Option<TestSet>		("t",		"testset",		"Comma-separated list of include filters.",								parseCommaSeparatedList)
    103 		   << Option<ExcludeSet>	("e",		"exclude",		"Comma-separated list of exclude filters.",								parseCommaSeparatedList, "")
    104 		   << Option<ContinueFile>	(DE_NULL,	"continue",		"Continue execution by initializing results from existing test log.")
    105 		   << Option<TestLogFile>	("o",		"out",			"Output test log filename.",											"TestLog.qpa")
    106 		   << Option<InfoLogFile>	("i",		"info",			"Output info log filename.",											"InfoLog.txt")
    107 		   << Option<Summary>		(DE_NULL,	"summary",		"Print summary after running tests.",									s_yesNo, "yes")
    108 		   << Option<BinaryName>	("b",		"binaryname",	"Test binary path. Relative to working directory.",						"<Unused>")
    109 		   << Option<WorkingDir>	("wd",		"workdir",		"Working directory for the test execution.",							".")
    110 		   << Option<CmdLineArgs>	(DE_NULL,	"cmdline",		"Additional command line arguments for the test binary.",				"");
    111 }
    112 
    113 } // opt
    114 
    115 enum RunMode
    116 {
    117 	RUNMODE_CONNECT,
    118 	RUNMODE_START_SERVER
    119 };
    120 
    121 struct CommandLine
    122 {
    123 	CommandLine (void)
    124 		: port		(0)
    125 		, summary	(false)
    126 	{
    127 	}
    128 
    129 	xe::TargetConfiguration	targetCfg;
    130 	RunMode					runMode;
    131 	string					serverBinOrAddress;
    132 	int						port;
    133 	string					caseListDir;
    134 	vector<string>			testset;
    135 	vector<string>			exclude;
    136 	string					inFile;
    137 	string					outFile;
    138 	string					infoFile;
    139 	bool					summary;
    140 };
    141 
    142 bool parseCommandLine (CommandLine& cmdLine, int argc, const char* const* argv)
    143 {
    144 	de::cmdline::Parser			parser;
    145 	de::cmdline::CommandLine	opts;
    146 
    147 	XE_CHECK(argc >= 1);
    148 
    149 	opt::registerOptions(parser);
    150 
    151 	if (!parser.parse(argc-1, argv+1, &opts, std::cerr))
    152 	{
    153 		std::cout << argv[0] << " [options]\n";
    154 		parser.help(std::cout);
    155 		return false;
    156 	}
    157 
    158 	if (opts.hasOption<opt::StartServer>() && opts.hasOption<opt::Host>())
    159 	{
    160 		std::cout << "Invalid command line arguments. Both --start-server and --connect defined." << std::endl;
    161 		return false;
    162 	}
    163 	else if (!opts.hasOption<opt::StartServer>() && !opts.hasOption<opt::Host>())
    164 	{
    165 		std::cout << "Invalid command line arguments. Must define either --start-server or --connect." << std::endl;
    166 		return false;
    167 	}
    168 
    169 	if (!opts.hasOption<opt::TestSet>())
    170 	{
    171 		std::cout << "Invalid command line arguments. --testset not defined." << std::endl;
    172 		return false;
    173 	}
    174 
    175 	if (opts.hasOption<opt::StartServer>())
    176 	{
    177 		cmdLine.runMode				= RUNMODE_START_SERVER;
    178 		cmdLine.serverBinOrAddress	= opts.getOption<opt::StartServer>();
    179 	}
    180 	else
    181 	{
    182 		cmdLine.runMode				= RUNMODE_CONNECT;
    183 		cmdLine.serverBinOrAddress	= opts.getOption<opt::Host>();
    184 	}
    185 
    186 	if (opts.hasOption<opt::ContinueFile>())
    187 	{
    188 		cmdLine.inFile = opts.getOption<opt::ContinueFile>();
    189 
    190 		if (cmdLine.inFile.empty())
    191 		{
    192 			std::cout << "Invalid command line arguments. --continue argument is empty." << std::endl;
    193 			return false;
    194 		}
    195 	}
    196 
    197 	cmdLine.port					= opts.getOption<opt::Port>();
    198 	cmdLine.caseListDir				= opts.getOption<opt::CaseListDir>();
    199 	cmdLine.testset					= opts.getOption<opt::TestSet>();
    200 	cmdLine.exclude					= opts.getOption<opt::ExcludeSet>();
    201 	cmdLine.outFile					= opts.getOption<opt::TestLogFile>();
    202 	cmdLine.infoFile				= opts.getOption<opt::InfoLogFile>();
    203 	cmdLine.summary					= opts.getOption<opt::Summary>();
    204 	cmdLine.targetCfg.binaryName	= opts.getOption<opt::BinaryName>();
    205 	cmdLine.targetCfg.workingDir	= opts.getOption<opt::WorkingDir>();
    206 	cmdLine.targetCfg.cmdLineArgs	= opts.getOption<opt::CmdLineArgs>();
    207 
    208 	return true;
    209 }
    210 
    211 bool checkCasePathPatternMatch (const char* pattern, const char* casePath, bool isTestGroup)
    212 {
    213 	int ptrnPos = 0;
    214 	int casePos = 0;
    215 
    216 	for (;;)
    217 	{
    218 		char c = casePath[casePos];
    219 		char p = pattern[ptrnPos];
    220 
    221 		if (p == '*')
    222 		{
    223 			/* Recurse to rest of positions. */
    224 			int next = casePos;
    225 			for (;;)
    226 			{
    227 				if (checkCasePathPatternMatch(pattern+ptrnPos+1, casePath+next, isTestGroup))
    228 					return DE_TRUE;
    229 
    230 				if (casePath[next] == 0)
    231 					return DE_FALSE; /* No match found. */
    232 				else
    233 					next += 1;
    234 			}
    235 			DE_ASSERT(DE_FALSE);
    236 		}
    237 		else if (c == 0 && p == 0)
    238 			return true;
    239 		else if (c == 0)
    240 		{
    241 			/* Incomplete match is ok for test groups. */
    242 			return isTestGroup;
    243 		}
    244 		else if (c != p)
    245 			return false;
    246 
    247 		casePos += 1;
    248 		ptrnPos += 1;
    249 	}
    250 
    251 	DE_ASSERT(false);
    252 	return false;
    253 }
    254 
    255 void readCaseList (xe::TestGroup* root, const char* filename)
    256 {
    257 	xe::TestCaseListParser	caseListParser;
    258 	std::ifstream			in				(filename, std::ios_base::binary);
    259 	deUint8					buf[1024];
    260 
    261 	XE_CHECK(in.good());
    262 
    263 	caseListParser.init(root);
    264 
    265 	for (;;)
    266 	{
    267 		in.read((char*)&buf[0], sizeof(buf));
    268 		int numRead = (int)in.gcount();
    269 
    270 		if (numRead > 0)
    271 			caseListParser.parse(&buf[0], numRead);
    272 
    273 		if (numRead < (int)sizeof(buf))
    274 			break; // EOF
    275 	}
    276 }
    277 
    278 void readCaseLists (xe::TestRoot& root, const char* caseListDir)
    279 {
    280 	int						testCaseListCount	= 0;
    281 	de::DirectoryIterator	iter				(caseListDir);
    282 
    283 	for (; iter.hasItem(); iter.next())
    284 	{
    285 		de::FilePath item = iter.getItem();
    286 
    287 		if (item.getType() == de::FilePath::TYPE_FILE)
    288 		{
    289 			string baseName = item.getBaseName();
    290 			if (baseName.find("-cases.xml") == baseName.length()-10)
    291 			{
    292 				string		packageName	= baseName.substr(0, baseName.length()-10);
    293 				xe::TestGroup*	package		= root.createGroup(packageName.c_str(), "");
    294 
    295 				readCaseList(package, item.getPath());
    296 				testCaseListCount++;
    297 			}
    298 		}
    299 	}
    300 
    301 	if (testCaseListCount == 0)
    302 		throw xe::Error("Couldn't find test case lists from test case list directory: '" + string(caseListDir)  + "'");
    303 }
    304 
    305 void addMatchingCases (const xe::TestGroup& group, xe::TestSet& testSet, const char* filter)
    306 {
    307 	for (int childNdx = 0; childNdx < group.getNumChildren(); childNdx++)
    308 	{
    309 		const xe::TestNode* child		= group.getChild(childNdx);
    310 		const bool			isGroup		= child->getNodeType() == xe::TESTNODETYPE_GROUP;
    311 		const string		fullPath	= child->getFullPath();
    312 
    313 		if (checkCasePathPatternMatch(filter, fullPath.c_str(), isGroup))
    314 		{
    315 			if (isGroup)
    316 			{
    317 				// Recurse into group.
    318 				addMatchingCases(static_cast<const xe::TestGroup&>(*child), testSet, filter);
    319 			}
    320 			else
    321 			{
    322 				DE_ASSERT(child->getNodeType() == xe::TESTNODETYPE_TEST_CASE);
    323 				testSet.add(child);
    324 			}
    325 		}
    326 	}
    327 }
    328 
    329 void removeMatchingCases (const xe::TestGroup& group, xe::TestSet& testSet, const char* filter)
    330 {
    331 	for (int childNdx = 0; childNdx < group.getNumChildren(); childNdx++)
    332 	{
    333 		const xe::TestNode* child		= group.getChild(childNdx);
    334 		const bool			isGroup		= child->getNodeType() == xe::TESTNODETYPE_GROUP;
    335 		const string		fullPath	= child->getFullPath();
    336 
    337 		if (checkCasePathPatternMatch(filter, fullPath.c_str(), isGroup))
    338 		{
    339 			if (isGroup)
    340 			{
    341 				// Recurse into group.
    342 				removeMatchingCases(static_cast<const xe::TestGroup&>(*child), testSet, filter);
    343 			}
    344 			else
    345 			{
    346 				DE_ASSERT(child->getNodeType() == xe::TESTNODETYPE_TEST_CASE);
    347 				testSet.remove(child);
    348 			}
    349 		}
    350 	}
    351 }
    352 
    353 class BatchResultHandler : public xe::TestLogHandler
    354 {
    355 public:
    356 	BatchResultHandler (xe::BatchResult* batchResult)
    357 		: m_batchResult(batchResult)
    358 	{
    359 	}
    360 
    361 	void setSessionInfo (const xe::SessionInfo& sessionInfo)
    362 	{
    363 		m_batchResult->getSessionInfo() = sessionInfo;
    364 	}
    365 
    366 	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
    367 	{
    368 		// \todo [2012-11-01 pyry] What to do with duplicate results?
    369 		if (m_batchResult->hasTestCaseResult(casePath))
    370 			return m_batchResult->getTestCaseResult(casePath);
    371 		else
    372 			return m_batchResult->createTestCaseResult(casePath);
    373 	}
    374 
    375 	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
    376 	{
    377 	}
    378 
    379 	void testCaseResultComplete (const xe::TestCaseResultPtr&)
    380 	{
    381 	}
    382 
    383 private:
    384 	xe::BatchResult* m_batchResult;
    385 };
    386 
    387 void readLogFile (xe::BatchResult* batchResult, const char* filename)
    388 {
    389 	std::ifstream		in		(filename, std::ifstream::binary|std::ifstream::in);
    390 	BatchResultHandler	handler	(batchResult);
    391 	xe::TestLogParser	parser	(&handler);
    392 	deUint8				buf		[1024];
    393 	int					numRead	= 0;
    394 
    395 	for (;;)
    396 	{
    397 		in.read((char*)&buf[0], DE_LENGTH_OF_ARRAY(buf));
    398 		numRead = (int)in.gcount();
    399 
    400 		if (numRead <= 0)
    401 			break;
    402 
    403 		parser.parse(&buf[0], numRead);
    404 	}
    405 
    406 	in.close();
    407 }
    408 
    409 void printBatchResultSummary (const xe::TestNode* root, const xe::TestSet& testSet, const xe::BatchResult& batchResult)
    410 {
    411 	int countByStatusCode[xe::TESTSTATUSCODE_LAST];
    412 	std::fill(&countByStatusCode[0], &countByStatusCode[0]+DE_LENGTH_OF_ARRAY(countByStatusCode), 0);
    413 
    414 	for (xe::ConstTestNodeIterator iter = xe::ConstTestNodeIterator::begin(root); iter != xe::ConstTestNodeIterator::end(root); ++iter)
    415 	{
    416 		const xe::TestNode* node = *iter;
    417 		if (node->getNodeType() == xe::TESTNODETYPE_TEST_CASE && testSet.hasNode(node))
    418 		{
    419 			const xe::TestCase*				testCase		= static_cast<const xe::TestCase*>(node);
    420 			string							fullPath;
    421 			xe::TestStatusCode				statusCode		= xe::TESTSTATUSCODE_PENDING;
    422 			testCase->getFullPath(fullPath);
    423 
    424 			// Parse result data if such exists.
    425 			if (batchResult.hasTestCaseResult(fullPath.c_str()))
    426 			{
    427 				xe::ConstTestCaseResultPtr	resultData	= batchResult.getTestCaseResult(fullPath.c_str());
    428 				xe::TestCaseResult			result;
    429 				xe::TestResultParser		parser;
    430 
    431 				xe::parseTestCaseResultFromData(&parser, &result, *resultData.get());
    432 				statusCode = result.statusCode;
    433 			}
    434 
    435 			countByStatusCode[statusCode] += 1;
    436 		}
    437 	}
    438 
    439 	printf("\nTest run summary:\n");
    440 	int totalCases = 0;
    441 	for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++)
    442 	{
    443 		if (countByStatusCode[code] > 0)
    444 			printf("  %20s: %5d\n", xe::getTestStatusCodeName((xe::TestStatusCode)code), countByStatusCode[code]);
    445 
    446 		totalCases += countByStatusCode[code];
    447 	}
    448 	printf("  %20s: %5d\n", "Total", totalCases);
    449 }
    450 
    451 void writeInfoLog (const xe::InfoLog& log, const char* filename)
    452 {
    453 	std::ofstream out(filename, std::ios_base::binary);
    454 	XE_CHECK(out.good());
    455 	out.write((const char*)log.getBytes(), log.getSize());
    456 	out.close();
    457 }
    458 
    459 xe::CommLink* createCommLink (const CommandLine& cmdLine)
    460 {
    461 	if (cmdLine.runMode == RUNMODE_START_SERVER)
    462 	{
    463 		xe::LocalTcpIpLink* link = new xe::LocalTcpIpLink();
    464 		try
    465 		{
    466 			link->start(cmdLine.serverBinOrAddress.c_str(), DE_NULL, cmdLine.port);
    467 			return link;
    468 		}
    469 		catch (...)
    470 		{
    471 			delete link;
    472 			throw;
    473 		}
    474 	}
    475 	else if (cmdLine.runMode == RUNMODE_CONNECT)
    476 	{
    477 		de::SocketAddress address;
    478 
    479 		address.setFamily(DE_SOCKETFAMILY_INET4);
    480 		address.setProtocol(DE_SOCKETPROTOCOL_TCP);
    481 		address.setHost(cmdLine.serverBinOrAddress.c_str());
    482 		address.setPort(cmdLine.port);
    483 
    484 		xe::TcpIpLink* link = new xe::TcpIpLink();
    485 		try
    486 		{
    487 			std::string error;
    488 
    489 			link->connect(address);
    490 			return link;
    491 		}
    492 		catch (const std::exception& error)
    493 		{
    494 			delete link;
    495 			throw xe::Error("Failed to connect to ExecServer at: " + cmdLine.serverBinOrAddress + ":" + de::toString(cmdLine.port) + ", " + error.what());
    496 		}
    497 		catch (...)
    498 		{
    499 			delete link;
    500 			throw;
    501 		}
    502 	}
    503 	else
    504 	{
    505 		DE_ASSERT(false);
    506 		return DE_NULL;
    507 	}
    508 }
    509 
    510 #if (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_ANDROID)
    511 
    512 static xe::BatchExecutor* s_executor = DE_NULL;
    513 
    514 void signalHandler (int, siginfo_t*, void*)
    515 {
    516 	if (s_executor)
    517 		s_executor->cancel();
    518 }
    519 
    520 void setupSignalHandler (xe::BatchExecutor* executor)
    521 {
    522 	s_executor = executor;
    523 	struct sigaction sa;
    524 
    525 	sa.sa_sigaction = signalHandler;
    526 	sa.sa_flags = SA_SIGINFO | SA_RESTART;
    527 	sigfillset(&sa.sa_mask);
    528 
    529 	sigaction(SIGINT, &sa, DE_NULL);
    530 }
    531 
    532 void resetSignalHandler (void)
    533 {
    534 	struct sigaction sa;
    535 
    536 	sa.sa_handler = SIG_DFL;
    537 	sa.sa_flags = SA_RESTART;
    538 	sigfillset(&sa.sa_mask);
    539 
    540 	sigaction(SIGINT, &sa, DE_NULL);
    541 	s_executor = DE_NULL;
    542 }
    543 
    544 #elif (DE_OS == DE_OS_WIN32)
    545 
    546 static xe::BatchExecutor* s_executor = DE_NULL;
    547 
    548 void signalHandler (int)
    549 {
    550 	if (s_executor)
    551 		s_executor->cancel();
    552 }
    553 
    554 void setupSignalHandler (xe::BatchExecutor* executor)
    555 {
    556 	s_executor = executor;
    557 	signal(SIGINT, signalHandler);
    558 }
    559 
    560 void resetSignalHandler (void)
    561 {
    562 	signal(SIGINT, SIG_DFL);
    563 	s_executor = DE_NULL;
    564 }
    565 
    566 #else
    567 
    568 void setupSignalHandler (xe::BatchExecutor*)
    569 {
    570 }
    571 
    572 void resetSignalHandler (void)
    573 {
    574 }
    575 
    576 #endif
    577 
    578 void runExecutor (const CommandLine& cmdLine)
    579 {
    580 	xe::TestRoot root;
    581 
    582 	// Read case list definitions.
    583 	readCaseLists(root, cmdLine.caseListDir.c_str());
    584 
    585 	// Build test set.
    586 	xe::TestSet testSet;
    587 
    588 	// Build test set.
    589 	for (vector<string>::const_iterator filterIter = cmdLine.testset.begin(); filterIter != cmdLine.testset.end(); ++filterIter)
    590 		addMatchingCases(root, testSet, filterIter->c_str());
    591 
    592 	if (testSet.empty())
    593 		throw xe::Error("None of the test case lists contains tests matching any of the test sets.");
    594 
    595 	// Remove excluded cases.
    596 	for (vector<string>::const_iterator filterIter = cmdLine.exclude.begin(); filterIter != cmdLine.exclude.end(); ++filterIter)
    597 		removeMatchingCases(root, testSet, filterIter->c_str());
    598 
    599 	// Initialize batch result.
    600 	xe::BatchResult	batchResult;
    601 	xe::InfoLog		infoLog;
    602 
    603 	// Read existing results from input file (if supplied).
    604 	if (!cmdLine.inFile.empty())
    605 		readLogFile(&batchResult, cmdLine.inFile.c_str());
    606 
    607 	// Initialize commLink.
    608 	de::UniquePtr<xe::CommLink> commLink(createCommLink(cmdLine));
    609 
    610 	xe::BatchExecutor executor(cmdLine.targetCfg, commLink.get(), &root, testSet, &batchResult, &infoLog);
    611 
    612 	try
    613 	{
    614 		setupSignalHandler(&executor);
    615 		executor.run();
    616 		resetSignalHandler();
    617 	}
    618 	catch (...)
    619 	{
    620 		resetSignalHandler();
    621 
    622 		if (!cmdLine.outFile.empty())
    623 		{
    624 			xe::writeBatchResultToFile(batchResult, cmdLine.outFile.c_str());
    625 			printf("Test log written to %s\n", cmdLine.outFile.c_str());
    626 		}
    627 
    628 		if (!cmdLine.infoFile.empty())
    629 		{
    630 			writeInfoLog(infoLog, cmdLine.infoFile.c_str());
    631 			printf("Info log written to %s\n", cmdLine.infoFile.c_str());
    632 		}
    633 
    634 		if (cmdLine.summary)
    635 			printBatchResultSummary(&root, testSet, batchResult);
    636 
    637 		throw;
    638 	}
    639 
    640 	if (!cmdLine.outFile.empty())
    641 	{
    642 		xe::writeBatchResultToFile(batchResult, cmdLine.outFile.c_str());
    643 		printf("Test log written to %s\n", cmdLine.outFile.c_str());
    644 	}
    645 
    646 	if (!cmdLine.infoFile.empty())
    647 	{
    648 		writeInfoLog(infoLog, cmdLine.infoFile.c_str());
    649 		printf("Info log written to %s\n", cmdLine.infoFile.c_str());
    650 	}
    651 
    652 	if (cmdLine.summary)
    653 		printBatchResultSummary(&root, testSet, batchResult);
    654 
    655 	{
    656 		string err;
    657 
    658 		if (commLink->getState(err) == xe::COMMLINKSTATE_ERROR)
    659 			throw xe::Error(err);
    660 	}
    661 }
    662 
    663 } // anonymous
    664 
    665 int main (int argc, const char* const* argv)
    666 {
    667 	CommandLine cmdLine;
    668 
    669 	if (!parseCommandLine(cmdLine, argc, argv))
    670 		return -1;
    671 
    672 	try
    673 	{
    674 		runExecutor(cmdLine);
    675 	}
    676 	catch (const std::exception& e)
    677 	{
    678 		printf("%s\n", e.what());
    679 		return -1;
    680 	}
    681 
    682 	return 0;
    683 }
    684