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