1 //
2 // Copyright 2019 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // TestSuite:
7 // Basic implementation of a test harness in ANGLE.
8
9 #include "TestSuite.h"
10
11 #include "common/debug.h"
12 #include "common/platform.h"
13 #include "common/string_utils.h"
14 #include "common/system_utils.h"
15 #include "util/Timer.h"
16
17 #include <time.h>
18 #include <fstream>
19
20 #include <gtest/gtest.h>
21 #include <rapidjson/document.h>
22 #include <rapidjson/filewritestream.h>
23 #include <rapidjson/istreamwrapper.h>
24 #include <rapidjson/prettywriter.h>
25
26 // We directly call into a function to register the parameterized tests. This saves spinning up
27 // a subprocess with a new gtest filter.
28 #include <gtest/../../src/gtest-internal-inl.h>
29
30 namespace js = rapidjson;
31
32 namespace angle
33 {
34 namespace
35 {
36 constexpr char kTestTimeoutArg[] = "--test-timeout=";
37 constexpr char kFilterFileArg[] = "--filter-file=";
38 constexpr char kResultFileArg[] = "--results-file=";
39 #if defined(NDEBUG)
40 constexpr int kDefaultTestTimeout = 20;
41 #else
42 constexpr int kDefaultTestTimeout = 60;
43 #endif
44 #if defined(NDEBUG)
45 constexpr int kDefaultBatchTimeout = 240;
46 #else
47 constexpr int kDefaultBatchTimeout = 600;
48 #endif
49 constexpr int kDefaultBatchSize = 1000;
50
ParseFlagValue(const char * flag,const char * argument)51 const char *ParseFlagValue(const char *flag, const char *argument)
52 {
53 if (strstr(argument, flag) == argument)
54 {
55 return argument + strlen(flag);
56 }
57
58 return nullptr;
59 }
60
ParseIntArg(const char * flag,const char * argument,int * valueOut)61 bool ParseIntArg(const char *flag, const char *argument, int *valueOut)
62 {
63 const char *value = ParseFlagValue(flag, argument);
64 if (!value)
65 {
66 return false;
67 }
68
69 char *end = nullptr;
70 const long longValue = strtol(value, &end, 10);
71
72 if (*end != '\0')
73 {
74 printf("Error parsing integer flag value.\n");
75 exit(1);
76 }
77
78 if (longValue == LONG_MAX || longValue == LONG_MIN || static_cast<int>(longValue) != longValue)
79 {
80 printf("Overflow when parsing integer flag value.\n");
81 exit(1);
82 }
83
84 *valueOut = static_cast<int>(longValue);
85 return true;
86 }
87
ParseFlag(const char * expected,const char * actual,bool * flagOut)88 bool ParseFlag(const char *expected, const char *actual, bool *flagOut)
89 {
90 if (strcmp(expected, actual) == 0)
91 {
92 *flagOut = true;
93 return true;
94 }
95 return false;
96 }
97
ParseStringArg(const char * flag,const char * argument,std::string * valueOut)98 bool ParseStringArg(const char *flag, const char *argument, std::string *valueOut)
99 {
100 const char *value = ParseFlagValue(flag, argument);
101 if (!value)
102 {
103 return false;
104 }
105
106 *valueOut = value;
107 return true;
108 }
109
DeleteArg(int * argc,char ** argv,int argIndex)110 void DeleteArg(int *argc, char **argv, int argIndex)
111 {
112 // Shift the remainder of the argv list left by one. Note that argv has (*argc + 1) elements,
113 // the last one always being NULL. The following loop moves the trailing NULL element as well.
114 for (int index = argIndex; index < *argc; ++index)
115 {
116 argv[index] = argv[index + 1];
117 }
118 (*argc)--;
119 }
120
AddArg(int * argc,char ** argv,const char * arg)121 void AddArg(int *argc, char **argv, const char *arg)
122 {
123 // This unsafe const_cast is necessary to work around gtest limitations.
124 argv[*argc] = const_cast<char *>(arg);
125 argv[*argc + 1] = nullptr;
126 (*argc)++;
127 }
128
ResultTypeToString(TestResultType type)129 const char *ResultTypeToString(TestResultType type)
130 {
131 switch (type)
132 {
133 case TestResultType::Crash:
134 return "CRASH";
135 case TestResultType::Fail:
136 return "FAIL";
137 case TestResultType::Pass:
138 return "PASS";
139 case TestResultType::Skip:
140 return "SKIP";
141 case TestResultType::Timeout:
142 return "TIMEOUT";
143 case TestResultType::Unknown:
144 return "UNKNOWN";
145 }
146 }
147
GetResultTypeFromString(const std::string & str)148 TestResultType GetResultTypeFromString(const std::string &str)
149 {
150 if (str == "CRASH")
151 return TestResultType::Crash;
152 if (str == "FAIL")
153 return TestResultType::Fail;
154 if (str == "PASS")
155 return TestResultType::Pass;
156 if (str == "SKIP")
157 return TestResultType::Skip;
158 if (str == "TIMEOUT")
159 return TestResultType::Timeout;
160 return TestResultType::Unknown;
161 }
162
ResultTypeToJSString(TestResultType type,js::Document::AllocatorType * allocator)163 js::Value ResultTypeToJSString(TestResultType type, js::Document::AllocatorType *allocator)
164 {
165 js::Value jsName;
166 jsName.SetString(ResultTypeToString(type), *allocator);
167 return jsName;
168 }
169
170 // Writes out a TestResults to the Chromium JSON Test Results format.
171 // https://chromium.googlesource.com/chromium/src.git/+/master/docs/testing/json_test_results_format.md
WriteTestResults(bool interrupted,const TestResults & testResults,const std::string & outputFile,const char * testSuiteName)172 void WriteTestResults(bool interrupted,
173 const TestResults &testResults,
174 const std::string &outputFile,
175 const char *testSuiteName)
176 {
177 time_t ltime;
178 time(<ime);
179 struct tm *timeinfo = gmtime(<ime);
180 ltime = mktime(timeinfo);
181
182 uint64_t secondsSinceEpoch = static_cast<uint64_t>(ltime);
183
184 js::Document doc;
185 doc.SetObject();
186
187 js::Document::AllocatorType &allocator = doc.GetAllocator();
188
189 doc.AddMember("interrupted", interrupted, allocator);
190 doc.AddMember("path_delimiter", ".", allocator);
191 doc.AddMember("version", 3, allocator);
192 doc.AddMember("seconds_since_epoch", secondsSinceEpoch, allocator);
193
194 js::Value testSuite;
195 testSuite.SetObject();
196
197 std::map<TestResultType, uint32_t> counts;
198
199 for (const auto &resultIter : testResults.results)
200 {
201 const TestIdentifier &id = resultIter.first;
202 const TestResult &result = resultIter.second;
203
204 js::Value jsResult;
205 jsResult.SetObject();
206
207 counts[result.type]++;
208
209 jsResult.AddMember("expected", "PASS", allocator);
210 jsResult.AddMember("actual", ResultTypeToJSString(result.type, &allocator), allocator);
211
212 js::Value times;
213 times.SetArray();
214 times.PushBack(result.elapsedTimeSeconds, allocator);
215
216 jsResult.AddMember("times", times, allocator);
217
218 char testName[500];
219 id.sprintfName(testName);
220 js::Value jsName;
221 jsName.SetString(testName, allocator);
222
223 testSuite.AddMember(jsName, jsResult, allocator);
224 }
225
226 js::Value numFailuresByType;
227 numFailuresByType.SetObject();
228
229 for (const auto &countIter : counts)
230 {
231 TestResultType type = countIter.first;
232 uint32_t count = countIter.second;
233
234 js::Value jsCount(count);
235 numFailuresByType.AddMember(ResultTypeToJSString(type, &allocator), jsCount, allocator);
236 }
237
238 doc.AddMember("num_failures_by_type", numFailuresByType, allocator);
239
240 js::Value tests;
241 tests.SetObject();
242 tests.AddMember(js::StringRef(testSuiteName), testSuite, allocator);
243
244 doc.AddMember("tests", tests, allocator);
245
246 printf("Writing test results to %s\n", outputFile.c_str());
247
248 FILE *fp = fopen(outputFile.c_str(), "w");
249
250 constexpr size_t kBufferSize = 0xFFFF;
251 std::vector<char> writeBuffer(kBufferSize);
252 js::FileWriteStream os(fp, writeBuffer.data(), kBufferSize);
253 js::PrettyWriter<js::FileWriteStream> writer(os);
254 doc.Accept(writer);
255
256 fclose(fp);
257 }
258
UpdateCurrentTestResult(const testing::TestResult & resultIn,TestResults * resultsOut)259 void UpdateCurrentTestResult(const testing::TestResult &resultIn, TestResults *resultsOut)
260 {
261 TestResult &resultOut = resultsOut->results[resultsOut->currentTest];
262
263 // Note: Crashes and Timeouts are detected by the crash handler and a watchdog thread.
264 if (resultIn.Skipped())
265 {
266 resultOut.type = TestResultType::Skip;
267 }
268 else if (resultIn.Failed())
269 {
270 resultOut.type = TestResultType::Fail;
271 }
272 else
273 {
274 resultOut.type = TestResultType::Pass;
275 }
276
277 resultOut.elapsedTimeSeconds = resultsOut->currentTestTimer.getElapsedTime();
278 }
279
GetTestIdentifier(const testing::TestInfo & testInfo)280 TestIdentifier GetTestIdentifier(const testing::TestInfo &testInfo)
281 {
282 return {testInfo.test_suite_name(), testInfo.name()};
283 }
284
285 class TestEventListener : public testing::EmptyTestEventListener
286 {
287 public:
288 // Note: TestResults is owned by the TestSuite. It should outlive TestEventListener.
TestEventListener(const std::string & outputFile,const char * testSuiteName,TestResults * testResults)289 TestEventListener(const std::string &outputFile,
290 const char *testSuiteName,
291 TestResults *testResults)
292 : mResultsFile(outputFile), mTestSuiteName(testSuiteName), mTestResults(testResults)
293 {}
294
OnTestStart(const testing::TestInfo & testInfo)295 void OnTestStart(const testing::TestInfo &testInfo) override
296 {
297 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
298 mTestResults->currentTest = GetTestIdentifier(testInfo);
299 mTestResults->currentTestTimer.start();
300 }
301
OnTestEnd(const testing::TestInfo & testInfo)302 void OnTestEnd(const testing::TestInfo &testInfo) override
303 {
304 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
305 mTestResults->currentTestTimer.stop();
306 const testing::TestResult &resultIn = *testInfo.result();
307 UpdateCurrentTestResult(resultIn, mTestResults);
308 mTestResults->currentTest = TestIdentifier();
309 }
310
OnTestProgramEnd(const testing::UnitTest & testProgramInfo)311 void OnTestProgramEnd(const testing::UnitTest &testProgramInfo) override
312 {
313 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
314 mTestResults->allDone = true;
315 WriteTestResults(false, *mTestResults, mResultsFile, mTestSuiteName);
316 }
317
318 private:
319 std::string mResultsFile;
320 const char *mTestSuiteName;
321 TestResults *mTestResults;
322 };
323
IsTestDisabled(const testing::TestInfo & testInfo)324 bool IsTestDisabled(const testing::TestInfo &testInfo)
325 {
326 return ::strstr(testInfo.name(), "DISABLED_") == testInfo.name();
327 }
328
329 using TestIdentifierFilter = std::function<bool(const TestIdentifier &id)>;
330
FilterTests(std::map<TestIdentifier,FileLine> * fileLinesOut,TestIdentifierFilter filter,bool alsoRunDisabledTests)331 std::vector<TestIdentifier> FilterTests(std::map<TestIdentifier, FileLine> *fileLinesOut,
332 TestIdentifierFilter filter,
333 bool alsoRunDisabledTests)
334 {
335 std::vector<TestIdentifier> tests;
336
337 const testing::UnitTest &testProgramInfo = *testing::UnitTest::GetInstance();
338 for (int suiteIndex = 0; suiteIndex < testProgramInfo.total_test_suite_count(); ++suiteIndex)
339 {
340 const testing::TestSuite &testSuite = *testProgramInfo.GetTestSuite(suiteIndex);
341 for (int testIndex = 0; testIndex < testSuite.total_test_count(); ++testIndex)
342 {
343 const testing::TestInfo &testInfo = *testSuite.GetTestInfo(testIndex);
344 TestIdentifier id = GetTestIdentifier(testInfo);
345 if (filter(id) && (!IsTestDisabled(testInfo) || alsoRunDisabledTests))
346 {
347 tests.emplace_back(id);
348
349 if (fileLinesOut)
350 {
351 (*fileLinesOut)[id] = {testInfo.file(), testInfo.line()};
352 }
353 }
354 }
355 }
356
357 return tests;
358 }
359
GetFilteredTests(std::map<TestIdentifier,FileLine> * fileLinesOut,bool alsoRunDisabledTests)360 std::vector<TestIdentifier> GetFilteredTests(std::map<TestIdentifier, FileLine> *fileLinesOut,
361 bool alsoRunDisabledTests)
362 {
363 TestIdentifierFilter gtestIDFilter = [](const TestIdentifier &id) {
364 return testing::internal::UnitTestOptions::FilterMatchesTest(id.testSuiteName, id.testName);
365 };
366
367 return FilterTests(fileLinesOut, gtestIDFilter, alsoRunDisabledTests);
368 }
369
GetShardTests(int shardIndex,int shardCount,std::map<TestIdentifier,FileLine> * fileLinesOut,bool alsoRunDisabledTests)370 std::vector<TestIdentifier> GetShardTests(int shardIndex,
371 int shardCount,
372 std::map<TestIdentifier, FileLine> *fileLinesOut,
373 bool alsoRunDisabledTests)
374 {
375 std::vector<TestIdentifier> allTests = GetFilteredTests(fileLinesOut, alsoRunDisabledTests);
376 std::vector<TestIdentifier> shardTests;
377
378 for (int testIndex = shardIndex; testIndex < static_cast<int>(allTests.size());
379 testIndex += shardCount)
380 {
381 shardTests.emplace_back(allTests[testIndex]);
382 }
383
384 return shardTests;
385 }
386
GetTestFilter(const std::vector<TestIdentifier> & tests)387 std::string GetTestFilter(const std::vector<TestIdentifier> &tests)
388 {
389 std::stringstream filterStream;
390
391 filterStream << "--gtest_filter=";
392
393 for (size_t testIndex = 0; testIndex < tests.size(); ++testIndex)
394 {
395 if (testIndex != 0)
396 {
397 filterStream << ":";
398 }
399
400 filterStream << tests[testIndex];
401 }
402
403 return filterStream.str();
404 }
405
ParseTestSuiteName(const char * executable)406 std::string ParseTestSuiteName(const char *executable)
407 {
408 const char *baseNameStart = strrchr(executable, GetPathSeparator());
409 if (!baseNameStart)
410 {
411 baseNameStart = executable;
412 }
413 else
414 {
415 baseNameStart++;
416 }
417
418 const char *suffix = GetExecutableExtension();
419 size_t suffixLen = strlen(suffix);
420 if (suffixLen == 0)
421 {
422 return baseNameStart;
423 }
424
425 if (!EndsWith(baseNameStart, suffix))
426 {
427 return baseNameStart;
428 }
429
430 return std::string(baseNameStart, baseNameStart + strlen(baseNameStart) - suffixLen);
431 }
432
GetTestResultsFromJSON(const js::Document & document,TestResults * resultsOut)433 bool GetTestResultsFromJSON(const js::Document &document, TestResults *resultsOut)
434 {
435 if (!document.HasMember("tests") || !document["tests"].IsObject())
436 {
437 return false;
438 }
439
440 const js::Value::ConstObject &tests = document["tests"].GetObject();
441 if (tests.MemberCount() != 1)
442 {
443 return false;
444 }
445
446 const js::Value::Member &suite = *tests.MemberBegin();
447 if (!suite.value.IsObject())
448 {
449 return false;
450 }
451
452 const js::Value::ConstObject &actual = suite.value.GetObject();
453
454 for (auto iter = actual.MemberBegin(); iter != actual.MemberEnd(); ++iter)
455 {
456 // Get test identifier.
457 const js::Value &name = iter->name;
458 if (!name.IsString())
459 {
460 return false;
461 }
462
463 TestIdentifier id;
464 if (!TestIdentifier::ParseFromString(name.GetString(), &id))
465 {
466 return false;
467 }
468
469 // Get test result.
470 const js::Value &value = iter->value;
471 if (!value.IsObject())
472 {
473 return false;
474 }
475
476 const js::Value::ConstObject &obj = value.GetObject();
477 if (!obj.HasMember("expected") || !obj.HasMember("actual"))
478 {
479 return false;
480 }
481
482 const js::Value &expected = obj["expected"];
483 const js::Value &actual = obj["actual"];
484
485 if (!expected.IsString() || !actual.IsString())
486 {
487 return false;
488 }
489
490 const std::string expectedStr = expected.GetString();
491 const std::string actualStr = actual.GetString();
492
493 if (expectedStr != "PASS")
494 {
495 return false;
496 }
497
498 TestResultType resultType = GetResultTypeFromString(actualStr);
499 if (resultType == TestResultType::Unknown)
500 {
501 return false;
502 }
503
504 double elapsedTimeSeconds = 0.0;
505 if (obj.HasMember("times"))
506 {
507 const js::Value × = obj["times"];
508 if (!times.IsArray())
509 {
510 return false;
511 }
512
513 const js::Value::ConstArray ×Array = times.GetArray();
514 if (timesArray.Size() != 1 || !timesArray[0].IsDouble())
515 {
516 return false;
517 }
518
519 elapsedTimeSeconds = timesArray[0].GetDouble();
520 }
521
522 TestResult &result = resultsOut->results[id];
523 result.elapsedTimeSeconds = elapsedTimeSeconds;
524 result.type = resultType;
525 }
526
527 return true;
528 }
529
MergeTestResults(const TestResults & input,TestResults * output)530 bool MergeTestResults(const TestResults &input, TestResults *output)
531 {
532 for (const auto &resultsIter : input.results)
533 {
534 const TestIdentifier &id = resultsIter.first;
535 const TestResult &inputResult = resultsIter.second;
536 TestResult &outputResult = output->results[id];
537
538 // This should probably handle situations where a test is run more than once.
539 if (inputResult.type != TestResultType::Skip)
540 {
541 if (outputResult.type != TestResultType::Skip)
542 {
543 printf("Warning: duplicate entry for %s.%s.\n", id.testSuiteName.c_str(),
544 id.testName.c_str());
545 return false;
546 }
547
548 outputResult.elapsedTimeSeconds = inputResult.elapsedTimeSeconds;
549 outputResult.type = inputResult.type;
550 }
551 }
552
553 return true;
554 }
555
PrintTestOutputSnippet(const TestIdentifier & id,const TestResult & result,const std::string & fullOutput)556 void PrintTestOutputSnippet(const TestIdentifier &id,
557 const TestResult &result,
558 const std::string &fullOutput)
559 {
560 std::stringstream nameStream;
561 nameStream << id;
562 std::string fullName = nameStream.str();
563
564 size_t runPos = fullOutput.find(std::string("[ RUN ] ") + fullName);
565 if (runPos == std::string::npos)
566 {
567 printf("Cannot locate test output snippet.\n");
568 return;
569 }
570
571 size_t endPos = fullOutput.find(std::string("[ FAILED ] ") + fullName, runPos);
572 // Only clip the snippet to the "OK" message if the test really
573 // succeeded. It still might have e.g. crashed after printing it.
574 if (endPos == std::string::npos && result.type == TestResultType::Pass)
575 {
576 endPos = fullOutput.find(std::string("[ OK ] ") + fullName, runPos);
577 }
578 if (endPos != std::string::npos)
579 {
580 size_t newline_pos = fullOutput.find("\n", endPos);
581 if (newline_pos != std::string::npos)
582 endPos = newline_pos + 1;
583 }
584
585 std::cout << "\n";
586 if (endPos != std::string::npos)
587 {
588 std::cout << fullOutput.substr(runPos, endPos - runPos);
589 }
590 else
591 {
592 std::cout << fullOutput.substr(runPos);
593 }
594 std::cout << "\n";
595 }
596 } // namespace
597
598 TestIdentifier::TestIdentifier() = default;
599
TestIdentifier(const std::string & suiteNameIn,const std::string & nameIn)600 TestIdentifier::TestIdentifier(const std::string &suiteNameIn, const std::string &nameIn)
601 : testSuiteName(suiteNameIn), testName(nameIn)
602 {}
603
604 TestIdentifier::TestIdentifier(const TestIdentifier &other) = default;
605
606 TestIdentifier::~TestIdentifier() = default;
607
608 TestIdentifier &TestIdentifier::operator=(const TestIdentifier &other) = default;
609
sprintfName(char * outBuffer) const610 void TestIdentifier::sprintfName(char *outBuffer) const
611 {
612 sprintf(outBuffer, "%s.%s", testSuiteName.c_str(), testName.c_str());
613 }
614
615 // static
ParseFromString(const std::string & str,TestIdentifier * idOut)616 bool TestIdentifier::ParseFromString(const std::string &str, TestIdentifier *idOut)
617 {
618 size_t separator = str.find(".");
619 if (separator == std::string::npos)
620 {
621 return false;
622 }
623
624 idOut->testSuiteName = str.substr(0, separator);
625 idOut->testName = str.substr(separator + 1, str.length() - separator - 1);
626 return true;
627 }
628
629 TestResults::TestResults() = default;
630
631 TestResults::~TestResults() = default;
632
633 ProcessInfo::ProcessInfo() = default;
634
operator =(ProcessInfo && rhs)635 ProcessInfo &ProcessInfo::operator=(ProcessInfo &&rhs)
636 {
637 process = std::move(rhs.process);
638 testsInBatch = std::move(rhs.testsInBatch);
639 resultsFileName = std::move(rhs.resultsFileName);
640 filterFileName = std::move(rhs.filterFileName);
641 commandLine = std::move(rhs.commandLine);
642 return *this;
643 }
644
645 ProcessInfo::~ProcessInfo() = default;
646
ProcessInfo(ProcessInfo && other)647 ProcessInfo::ProcessInfo(ProcessInfo &&other)
648 {
649 *this = std::move(other);
650 }
651
TestSuite(int * argc,char ** argv)652 TestSuite::TestSuite(int *argc, char **argv)
653 : mShardCount(-1),
654 mShardIndex(-1),
655 mBotMode(false),
656 mBatchSize(kDefaultBatchSize),
657 mCurrentResultCount(0),
658 mTotalResultCount(0),
659 mMaxProcesses(NumberOfProcessors()),
660 mTestTimeout(kDefaultTestTimeout),
661 mBatchTimeout(kDefaultBatchTimeout)
662 {
663 bool hasFilter = false;
664 bool alsoRunDisabledTests = false;
665
666 #if defined(ANGLE_PLATFORM_WINDOWS)
667 testing::GTEST_FLAG(catch_exceptions) = false;
668 #endif
669
670 // Note that the crash callback must be owned and not use global constructors.
671 mCrashCallback = [this]() { onCrashOrTimeout(TestResultType::Crash); };
672 InitCrashHandler(&mCrashCallback);
673
674 if (*argc <= 0)
675 {
676 printf("Missing test arguments.\n");
677 exit(1);
678 }
679
680 mTestExecutableName = argv[0];
681 mTestSuiteName = ParseTestSuiteName(mTestExecutableName.c_str());
682
683 for (int argIndex = 1; argIndex < *argc;)
684 {
685 if (parseSingleArg(argv[argIndex]))
686 {
687 DeleteArg(argc, argv, argIndex);
688 continue;
689 }
690
691 if (ParseFlagValue("--gtest_filter=", argv[argIndex]))
692 {
693 hasFilter = true;
694 }
695 else
696 {
697 // Don't include disabled tests in test lists unless the user asks for them.
698 if (strcmp("--gtest_also_run_disabled_tests", argv[argIndex]) == 0)
699 {
700 alsoRunDisabledTests = true;
701 }
702
703 mGoogleTestCommandLineArgs.push_back(argv[argIndex]);
704 }
705 ++argIndex;
706 }
707
708 if ((mShardIndex >= 0) != (mShardCount > 1))
709 {
710 printf("Shard index and shard count must be specified together.\n");
711 exit(1);
712 }
713
714 if (!mFilterFile.empty())
715 {
716 if (hasFilter)
717 {
718 printf("Cannot use gtest_filter in conjunction with a filter file.\n");
719 exit(1);
720 }
721
722 uint32_t fileSize = 0;
723 if (!GetFileSize(mFilterFile.c_str(), &fileSize))
724 {
725 printf("Error getting filter file size: %s\n", mFilterFile.c_str());
726 exit(1);
727 }
728
729 std::vector<char> fileContents(fileSize + 1, 0);
730 if (!ReadEntireFileToString(mFilterFile.c_str(), fileContents.data(), fileSize))
731 {
732 printf("Error loading filter file: %s\n", mFilterFile.c_str());
733 exit(1);
734 }
735 mFilterString.assign(fileContents.data());
736
737 if (mFilterString.substr(0, strlen("--gtest_filter=")) != std::string("--gtest_filter="))
738 {
739 printf("Filter file must start with \"--gtest_filter=\".");
740 exit(1);
741 }
742
743 // Note that we only add a filter string if we previously deleted a shader filter file
744 // argument. So we will have space for the new filter string in argv.
745 AddArg(argc, argv, mFilterString.c_str());
746 }
747
748 if (mShardCount > 0)
749 {
750 // Call into gtest internals to force parameterized test name registration.
751 testing::internal::UnitTestImpl *impl = testing::internal::GetUnitTestImpl();
752 impl->RegisterParameterizedTests();
753
754 // Initialize internal GoogleTest filter arguments so we can call "FilterMatchesTest".
755 testing::internal::ParseGoogleTestFlagsOnly(argc, argv);
756
757 mTestQueue = GetShardTests(mShardIndex, mShardCount, &mTestFileLines, alsoRunDisabledTests);
758 mFilterString = GetTestFilter(mTestQueue);
759
760 // Note that we only add a filter string if we previously deleted a shader index/count
761 // argument. So we will have space for the new filter string in argv.
762 AddArg(argc, argv, mFilterString.c_str());
763
764 // Force-re-initialize GoogleTest flags to load the shard filter.
765 testing::internal::ParseGoogleTestFlagsOnly(argc, argv);
766 }
767
768 testing::InitGoogleTest(argc, argv);
769
770 if (mShardCount <= 0)
771 {
772 mTestQueue = GetFilteredTests(&mTestFileLines, alsoRunDisabledTests);
773 }
774
775 mTotalResultCount = mTestQueue.size();
776
777 if ((mBotMode || !mResultsDirectory.empty()) && mResultsFile.empty())
778 {
779 // Create a default output file in bot mode.
780 mResultsFile = "output.json";
781 }
782
783 if (!mResultsDirectory.empty())
784 {
785 std::stringstream resultFileName;
786 resultFileName << mResultsDirectory << GetPathSeparator() << mResultsFile;
787 mResultsFile = resultFileName.str();
788 }
789
790 if (!mResultsFile.empty())
791 {
792 testing::TestEventListeners &listeners = testing::UnitTest::GetInstance()->listeners();
793 listeners.Append(
794 new TestEventListener(mResultsFile, mTestSuiteName.c_str(), &mTestResults));
795
796 std::vector<TestIdentifier> testList = GetFilteredTests(nullptr, alsoRunDisabledTests);
797
798 for (const TestIdentifier &id : testList)
799 {
800 mTestResults.results[id].type = TestResultType::Skip;
801 }
802 }
803 }
804
~TestSuite()805 TestSuite::~TestSuite()
806 {
807 if (mWatchdogThread.joinable())
808 {
809 mWatchdogThread.detach();
810 }
811 TerminateCrashHandler();
812 }
813
parseSingleArg(const char * argument)814 bool TestSuite::parseSingleArg(const char *argument)
815 {
816 return (ParseIntArg("--shard-count=", argument, &mShardCount) ||
817 ParseIntArg("--shard-index=", argument, &mShardIndex) ||
818 ParseIntArg("--batch-size=", argument, &mBatchSize) ||
819 ParseIntArg("--max-processes=", argument, &mMaxProcesses) ||
820 ParseIntArg(kTestTimeoutArg, argument, &mTestTimeout) ||
821 ParseIntArg("--batch-timeout=", argument, &mBatchTimeout) ||
822 ParseStringArg("--results-directory=", argument, &mResultsDirectory) ||
823 ParseStringArg(kResultFileArg, argument, &mResultsFile) ||
824 ParseStringArg(kFilterFileArg, argument, &mFilterFile) ||
825 ParseFlag("--bot-mode", argument, &mBotMode));
826 }
827
onCrashOrTimeout(TestResultType crashOrTimeout)828 void TestSuite::onCrashOrTimeout(TestResultType crashOrTimeout)
829 {
830 if (mTestResults.currentTest.valid())
831 {
832 TestResult &result = mTestResults.results[mTestResults.currentTest];
833 result.type = crashOrTimeout;
834 result.elapsedTimeSeconds = mTestResults.currentTestTimer.getElapsedTime();
835 }
836
837 if (mResultsFile.empty())
838 {
839 printf("No results file specified.\n");
840 return;
841 }
842
843 WriteTestResults(true, mTestResults, mResultsFile, mTestSuiteName.c_str());
844 }
845
launchChildTestProcess(const std::vector<TestIdentifier> & testsInBatch)846 bool TestSuite::launchChildTestProcess(const std::vector<TestIdentifier> &testsInBatch)
847 {
848 constexpr uint32_t kMaxPath = 1000;
849
850 // Create a temporary file to store the test list
851 ProcessInfo processInfo;
852
853 char filterBuffer[kMaxPath] = {};
854 if (!CreateTemporaryFile(filterBuffer, kMaxPath))
855 {
856 std::cerr << "Error creating temporary file for test list.\n";
857 return false;
858 }
859 processInfo.filterFileName.assign(filterBuffer);
860
861 std::string filterString = GetTestFilter(testsInBatch);
862
863 FILE *fp = fopen(processInfo.filterFileName.c_str(), "w");
864 if (!fp)
865 {
866 std::cerr << "Error opening temporary file for test list.\n";
867 return false;
868 }
869 fprintf(fp, "%s", filterString.c_str());
870 fclose(fp);
871
872 std::string filterFileArg = kFilterFileArg + processInfo.filterFileName;
873
874 // Create a temporary file to store the test output.
875 char resultsBuffer[kMaxPath] = {};
876 if (!CreateTemporaryFile(resultsBuffer, kMaxPath))
877 {
878 std::cerr << "Error creating temporary file for test list.\n";
879 return false;
880 }
881 processInfo.resultsFileName.assign(resultsBuffer);
882
883 std::string resultsFileArg = kResultFileArg + processInfo.resultsFileName;
884
885 // Construct commandline for child process.
886 std::vector<const char *> args;
887
888 args.push_back(mTestExecutableName.c_str());
889 args.push_back(filterFileArg.c_str());
890 args.push_back(resultsFileArg.c_str());
891
892 for (const std::string &arg : mGoogleTestCommandLineArgs)
893 {
894 args.push_back(arg.c_str());
895 }
896
897 std::string timeoutStr;
898 if (mTestTimeout != kDefaultTestTimeout)
899 {
900 std::stringstream timeoutStream;
901 timeoutStream << kTestTimeoutArg << mTestTimeout;
902 timeoutStr = timeoutStream.str();
903 args.push_back(timeoutStr.c_str());
904 }
905
906 // Launch child process and wait for completion.
907 processInfo.process = LaunchProcess(args, true, true);
908
909 if (!processInfo.process->started())
910 {
911 std::cerr << "Error launching child process.\n";
912 return false;
913 }
914
915 std::stringstream commandLineStr;
916 for (const char *arg : args)
917 {
918 commandLineStr << arg << " ";
919 }
920
921 processInfo.commandLine = commandLineStr.str();
922 processInfo.testsInBatch = testsInBatch;
923 mCurrentProcesses.emplace_back(std::move(processInfo));
924 return true;
925 }
926
finishProcess(ProcessInfo * processInfo)927 bool TestSuite::finishProcess(ProcessInfo *processInfo)
928 {
929 // Get test results and merge into master list.
930 TestResults batchResults;
931
932 if (!GetTestResultsFromFile(processInfo->resultsFileName.c_str(), &batchResults))
933 {
934 std::cerr << "Error reading test results from child process.\n";
935 return false;
936 }
937
938 if (!MergeTestResults(batchResults, &mTestResults))
939 {
940 std::cerr << "Error merging batch test results.\n";
941 return false;
942 }
943
944 // Process results and print unexpected errors.
945 for (const auto &resultIter : batchResults.results)
946 {
947 const TestIdentifier &id = resultIter.first;
948 const TestResult &result = resultIter.second;
949
950 // Skip results aren't procesed since they're added back to the test queue below.
951 if (result.type == TestResultType::Skip)
952 {
953 continue;
954 }
955
956 mCurrentResultCount++;
957 printf("[%d/%d] %s.%s", mCurrentResultCount, mTotalResultCount, id.testSuiteName.c_str(),
958 id.testName.c_str());
959
960 if (result.type == TestResultType::Pass)
961 {
962 printf(" (%g ms)\n", result.elapsedTimeSeconds * 1000.0);
963 }
964 else
965 {
966 printf(" (%s)\n", ResultTypeToString(result.type));
967
968 const std::string &batchStdout = processInfo->process->getStdout();
969 PrintTestOutputSnippet(id, result, batchStdout);
970 }
971 }
972
973 // On unexpected exit, re-queue any unfinished tests.
974 if (processInfo->process->getExitCode() != 0)
975 {
976 for (const auto &resultIter : batchResults.results)
977 {
978 const TestIdentifier &id = resultIter.first;
979 const TestResult &result = resultIter.second;
980
981 if (result.type == TestResultType::Skip)
982 {
983 mTestQueue.emplace_back(id);
984 }
985 }
986 }
987
988 // Clean up any dirty temporary files.
989 for (const std::string &tempFile : {processInfo->filterFileName, processInfo->resultsFileName})
990 {
991 // Note: we should be aware that this cleanup won't happen if the harness itself crashes.
992 // If this situation comes up in the future we should add crash cleanup to the harness.
993 if (!angle::DeleteFile(tempFile.c_str()))
994 {
995 std::cerr << "Warning: Error cleaning up temp file: " << tempFile << "\n";
996 }
997 }
998
999 processInfo->process.reset();
1000 return true;
1001 }
1002
run()1003 int TestSuite::run()
1004 {
1005 // Run tests serially.
1006 if (!mBotMode)
1007 {
1008 startWatchdog();
1009 return RUN_ALL_TESTS();
1010 }
1011
1012 constexpr double kIdleMessageTimeout = 5.0;
1013
1014 Timer messageTimer;
1015 messageTimer.start();
1016
1017 while (!mTestQueue.empty() || !mCurrentProcesses.empty())
1018 {
1019 bool progress = false;
1020
1021 // Spawn a process if needed and possible.
1022 while (static_cast<int>(mCurrentProcesses.size()) < mMaxProcesses && !mTestQueue.empty())
1023 {
1024 int numTests = std::min<int>(mTestQueue.size(), mBatchSize);
1025
1026 std::vector<TestIdentifier> testsInBatch;
1027 testsInBatch.assign(mTestQueue.begin(), mTestQueue.begin() + numTests);
1028 mTestQueue.erase(mTestQueue.begin(), mTestQueue.begin() + numTests);
1029
1030 if (!launchChildTestProcess(testsInBatch))
1031 {
1032 return 1;
1033 }
1034
1035 progress = true;
1036 }
1037
1038 // Check for process completion.
1039 for (auto processIter = mCurrentProcesses.begin(); processIter != mCurrentProcesses.end();)
1040 {
1041 ProcessInfo &processInfo = *processIter;
1042 if (processInfo.process->finished())
1043 {
1044 if (!finishProcess(&processInfo))
1045 {
1046 return 1;
1047 }
1048 processIter = mCurrentProcesses.erase(processIter);
1049 progress = true;
1050 }
1051 else if (processInfo.process->getElapsedTimeSeconds() > mBatchTimeout)
1052 {
1053 // Terminate the process and record timeouts for the batch.
1054 // Because we can't determine which sub-test caused a timeout, record the whole
1055 // batch as a timeout failure. Can be improved by using socket message passing.
1056 if (!processInfo.process->kill())
1057 {
1058 return 1;
1059 }
1060 for (const TestIdentifier &testIdentifier : processInfo.testsInBatch)
1061 {
1062 // Because the whole batch failed we can't know how long each test took.
1063 mTestResults.results[testIdentifier].type = TestResultType::Timeout;
1064 }
1065
1066 processIter = mCurrentProcesses.erase(processIter);
1067 progress = true;
1068 }
1069 else
1070 {
1071 processIter++;
1072 }
1073 }
1074
1075 if (!progress && messageTimer.getElapsedTime() > kIdleMessageTimeout)
1076 {
1077 for (const ProcessInfo &processInfo : mCurrentProcesses)
1078 {
1079 double processTime = processInfo.process->getElapsedTimeSeconds();
1080 if (processTime > kIdleMessageTimeout)
1081 {
1082 printf("Running for %d seconds: %s\n", static_cast<int>(processTime),
1083 processInfo.commandLine.c_str());
1084 }
1085 }
1086
1087 messageTimer.start();
1088 }
1089
1090 // Sleep briefly and continue.
1091 angle::Sleep(10);
1092 }
1093
1094 // Dump combined results.
1095 WriteTestResults(true, mTestResults, mResultsFile, mTestSuiteName.c_str());
1096
1097 return printFailuresAndReturnCount() == 0;
1098 }
1099
printFailuresAndReturnCount() const1100 int TestSuite::printFailuresAndReturnCount() const
1101 {
1102 std::vector<std::string> failures;
1103
1104 for (const auto &resultIter : mTestResults.results)
1105 {
1106 const TestIdentifier &id = resultIter.first;
1107 const TestResult &result = resultIter.second;
1108
1109 if (result.type != TestResultType::Pass)
1110 {
1111 const FileLine &fileLine = mTestFileLines.find(id)->second;
1112
1113 std::stringstream failureMessage;
1114 failureMessage << id << " (" << fileLine.file << ":" << fileLine.line << ") ("
1115 << ResultTypeToString(result.type) << ")";
1116 failures.emplace_back(failureMessage.str());
1117 }
1118 }
1119
1120 if (failures.empty())
1121 return 0;
1122
1123 printf("%zu test%s failed:\n", failures.size(), failures.size() > 1 ? "s" : "");
1124 for (const std::string &failure : failures)
1125 {
1126 printf(" %s\n", failure.c_str());
1127 }
1128
1129 return static_cast<int>(failures.size());
1130 }
1131
startWatchdog()1132 void TestSuite::startWatchdog()
1133 {
1134 auto watchdogMain = [this]() {
1135 do
1136 {
1137 {
1138 std::lock_guard<std::mutex> guard(mTestResults.currentTestMutex);
1139 if (mTestResults.currentTestTimer.getElapsedTime() >
1140 static_cast<double>(mTestTimeout))
1141 {
1142 onCrashOrTimeout(TestResultType::Timeout);
1143 exit(2);
1144 }
1145
1146 if (mTestResults.allDone)
1147 return;
1148 }
1149
1150 angle::Sleep(1000);
1151 } while (true);
1152 };
1153 mWatchdogThread = std::thread(watchdogMain);
1154 }
1155
GetTestResultsFromFile(const char * fileName,TestResults * resultsOut)1156 bool GetTestResultsFromFile(const char *fileName, TestResults *resultsOut)
1157 {
1158 std::ifstream ifs(fileName);
1159 if (!ifs.is_open())
1160 {
1161 std::cerr << "Error opening " << fileName << "\n";
1162 return false;
1163 }
1164
1165 js::IStreamWrapper ifsWrapper(ifs);
1166 js::Document document;
1167 document.ParseStream(ifsWrapper);
1168
1169 if (document.HasParseError())
1170 {
1171 std::cerr << "Parse error reading JSON document: " << document.GetParseError() << "\n";
1172 return false;
1173 }
1174
1175 if (!GetTestResultsFromJSON(document, resultsOut))
1176 {
1177 std::cerr << "Error getting test results from JSON.\n";
1178 return false;
1179 }
1180
1181 return true;
1182 }
1183
TestResultTypeToString(TestResultType type)1184 const char *TestResultTypeToString(TestResultType type)
1185 {
1186 switch (type)
1187 {
1188 case TestResultType::Crash:
1189 return "Crash";
1190 case TestResultType::Fail:
1191 return "Fail";
1192 case TestResultType::Skip:
1193 return "Skip";
1194 case TestResultType::Pass:
1195 return "Pass";
1196 case TestResultType::Timeout:
1197 return "Timeout";
1198 case TestResultType::Unknown:
1199 return "Unknown";
1200 }
1201 }
1202 } // namespace angle
1203